@grida/refig 0.0.3 → 0.0.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.
@@ -6784,6 +6784,43 @@ var b4 = function(d, b) {
6784
6784
  var b8 = function(d, b) {
6785
6785
  return b4(d, b) + b4(d, b + 4) * 4294967296;
6786
6786
  };
6787
+ var Inflate = /* @__PURE__ */ (function() {
6788
+ function Inflate2(opts, cb) {
6789
+ if (typeof opts == "function")
6790
+ cb = opts, opts = {};
6791
+ this.ondata = cb;
6792
+ var dict = opts && opts.dictionary && opts.dictionary.subarray(-32768);
6793
+ this.s = { i: 0, b: dict ? dict.length : 0 };
6794
+ this.o = new u8(32768);
6795
+ this.p = new u8(0);
6796
+ if (dict)
6797
+ this.o.set(dict);
6798
+ }
6799
+ Inflate2.prototype.e = function(c) {
6800
+ if (!this.ondata)
6801
+ err(5);
6802
+ if (this.d)
6803
+ err(4);
6804
+ if (!this.p.length)
6805
+ this.p = c;
6806
+ else if (c.length) {
6807
+ var n = new u8(this.p.length + c.length);
6808
+ n.set(this.p), n.set(c, this.p.length), this.p = n;
6809
+ }
6810
+ };
6811
+ Inflate2.prototype.c = function(final) {
6812
+ this.s.i = +(this.d = final || false);
6813
+ var bts = this.s.b;
6814
+ var dt = inflt(this.p, this.s, this.o);
6815
+ this.ondata(slc(dt, bts, this.s.b), this.d);
6816
+ this.o = slc(dt, this.s.b - 32768), this.s.b = this.o.length;
6817
+ this.p = slc(this.p, this.s.p / 8 | 0), this.s.p &= 7;
6818
+ };
6819
+ Inflate2.prototype.push = function(chunk, final) {
6820
+ this.e(chunk), this.c(final);
6821
+ };
6822
+ return Inflate2;
6823
+ })();
6787
6824
  function inflateSync(data, opts) {
6788
6825
  return inflt(data, { i: 2 }, opts && opts.out, opts && opts.dictionary);
6789
6826
  }
@@ -6838,6 +6875,165 @@ var z64e = function(d, b) {
6838
6875
  ;
6839
6876
  return [b8(d, b + 12), b8(d, b + 4), b8(d, b + 20)];
6840
6877
  };
6878
+ var UnzipPassThrough = /* @__PURE__ */ (function() {
6879
+ function UnzipPassThrough2() {
6880
+ }
6881
+ UnzipPassThrough2.prototype.push = function(data, final) {
6882
+ this.ondata(null, data, final);
6883
+ };
6884
+ UnzipPassThrough2.compression = 0;
6885
+ return UnzipPassThrough2;
6886
+ })();
6887
+ var UnzipInflate = /* @__PURE__ */ (function() {
6888
+ function UnzipInflate2() {
6889
+ var _this = this;
6890
+ this.i = new Inflate(function(dat, final) {
6891
+ _this.ondata(null, dat, final);
6892
+ });
6893
+ }
6894
+ UnzipInflate2.prototype.push = function(data, final) {
6895
+ try {
6896
+ this.i.push(data, final);
6897
+ } catch (e) {
6898
+ this.ondata(e, null, final);
6899
+ }
6900
+ };
6901
+ UnzipInflate2.compression = 8;
6902
+ return UnzipInflate2;
6903
+ })();
6904
+ var Unzip = /* @__PURE__ */ (function() {
6905
+ function Unzip2(cb) {
6906
+ this.onfile = cb;
6907
+ this.k = [];
6908
+ this.o = {
6909
+ 0: UnzipPassThrough
6910
+ };
6911
+ this.p = et;
6912
+ }
6913
+ Unzip2.prototype.push = function(chunk, final) {
6914
+ var _this = this;
6915
+ if (!this.onfile)
6916
+ err(5);
6917
+ if (!this.p)
6918
+ err(4);
6919
+ if (this.c > 0) {
6920
+ var len = Math.min(this.c, chunk.length);
6921
+ var toAdd = chunk.subarray(0, len);
6922
+ this.c -= len;
6923
+ if (this.d)
6924
+ this.d.push(toAdd, !this.c);
6925
+ else
6926
+ this.k[0].push(toAdd);
6927
+ chunk = chunk.subarray(len);
6928
+ if (chunk.length)
6929
+ return this.push(chunk, final);
6930
+ } else {
6931
+ var f = 0, i = 0, is = void 0, buf = void 0;
6932
+ if (!this.p.length)
6933
+ buf = chunk;
6934
+ else if (!chunk.length)
6935
+ buf = this.p;
6936
+ else {
6937
+ buf = new u8(this.p.length + chunk.length);
6938
+ buf.set(this.p), buf.set(chunk, this.p.length);
6939
+ }
6940
+ var l = buf.length, oc = this.c, add = oc && this.d;
6941
+ var _loop_2 = function() {
6942
+ var _a2;
6943
+ var sig = b4(buf, i);
6944
+ if (sig == 67324752) {
6945
+ f = 1, is = i;
6946
+ this_1.d = null;
6947
+ this_1.c = 0;
6948
+ var bf = b2(buf, i + 6), cmp_1 = b2(buf, i + 8), u = bf & 2048, dd = bf & 8, fnl = b2(buf, i + 26), es = b2(buf, i + 28);
6949
+ if (l > i + 30 + fnl + es) {
6950
+ var chks_3 = [];
6951
+ this_1.k.unshift(chks_3);
6952
+ f = 2;
6953
+ var sc_1 = b4(buf, i + 18), su_1 = b4(buf, i + 22);
6954
+ var fn_1 = strFromU8(buf.subarray(i + 30, i += 30 + fnl), !u);
6955
+ if (sc_1 == 4294967295) {
6956
+ _a2 = dd ? [-2] : z64e(buf, i), sc_1 = _a2[0], su_1 = _a2[1];
6957
+ } else if (dd)
6958
+ sc_1 = -1;
6959
+ i += es;
6960
+ this_1.c = sc_1;
6961
+ var d_1;
6962
+ var file_1 = {
6963
+ name: fn_1,
6964
+ compression: cmp_1,
6965
+ start: function() {
6966
+ if (!file_1.ondata)
6967
+ err(5);
6968
+ if (!sc_1)
6969
+ file_1.ondata(null, et, true);
6970
+ else {
6971
+ var ctr = _this.o[cmp_1];
6972
+ if (!ctr)
6973
+ file_1.ondata(err(14, "unknown compression type " + cmp_1, 1), null, false);
6974
+ d_1 = sc_1 < 0 ? new ctr(fn_1) : new ctr(fn_1, sc_1, su_1);
6975
+ d_1.ondata = function(err3, dat3, final2) {
6976
+ file_1.ondata(err3, dat3, final2);
6977
+ };
6978
+ for (var _i = 0, chks_4 = chks_3; _i < chks_4.length; _i++) {
6979
+ var dat2 = chks_4[_i];
6980
+ d_1.push(dat2, false);
6981
+ }
6982
+ if (_this.k[0] == chks_3 && _this.c)
6983
+ _this.d = d_1;
6984
+ else
6985
+ d_1.push(et, true);
6986
+ }
6987
+ },
6988
+ terminate: function() {
6989
+ if (d_1 && d_1.terminate)
6990
+ d_1.terminate();
6991
+ }
6992
+ };
6993
+ if (sc_1 >= 0)
6994
+ file_1.size = sc_1, file_1.originalSize = su_1;
6995
+ this_1.onfile(file_1);
6996
+ }
6997
+ return "break";
6998
+ } else if (oc) {
6999
+ if (sig == 134695760) {
7000
+ is = i += 12 + (oc == -2 && 8), f = 3, this_1.c = 0;
7001
+ return "break";
7002
+ } else if (sig == 33639248) {
7003
+ is = i -= 4, f = 3, this_1.c = 0;
7004
+ return "break";
7005
+ }
7006
+ }
7007
+ };
7008
+ var this_1 = this;
7009
+ for (; i < l - 4; ++i) {
7010
+ var state_1 = _loop_2();
7011
+ if (state_1 === "break")
7012
+ break;
7013
+ }
7014
+ this.p = et;
7015
+ if (oc < 0) {
7016
+ var dat = f ? buf.subarray(0, is - 12 - (oc == -2 && 8) - (b4(buf, is - 16) == 134695760 && 4)) : buf.subarray(0, i);
7017
+ if (add)
7018
+ add.push(dat, !!f);
7019
+ else
7020
+ this.k[+(f == 2)].push(dat);
7021
+ }
7022
+ if (f & 2)
7023
+ return this.push(buf.subarray(i), final);
7024
+ this.p = buf.subarray(i);
7025
+ }
7026
+ if (final) {
7027
+ if (this.c)
7028
+ err(13);
7029
+ this.p = null;
7030
+ }
7031
+ };
7032
+ Unzip2.prototype.register = function(decoder) {
7033
+ this.o[decoder.compression] = decoder;
7034
+ };
7035
+ return Unzip2;
7036
+ })();
6841
7037
  function unzipSync(data, opts) {
6842
7038
  var files = {};
6843
7039
  var e = data.length - 22;
@@ -13285,6 +13481,93 @@ function readFigFile(data) {
13285
13481
  }
13286
13482
  return { ...parseFigData(archiveData), zip_files: zipFiles };
13287
13483
  }
13484
+ async function readFigFileFromStream(stream) {
13485
+ const zipFiles = {};
13486
+ const unzip = new Unzip((file) => {
13487
+ const fileChunks = [];
13488
+ file.ondata = (err3, data, final) => {
13489
+ if (err3) throw err3;
13490
+ if (data && data.length > 0) fileChunks.push(data);
13491
+ if (final) {
13492
+ const size = fileChunks.reduce((s, c) => s + c.length, 0);
13493
+ const result = new Uint8Array(size);
13494
+ let off = 0;
13495
+ for (const c of fileChunks) {
13496
+ result.set(c, off);
13497
+ off += c.length;
13498
+ }
13499
+ zipFiles[file.name] = result;
13500
+ }
13501
+ };
13502
+ file.start();
13503
+ });
13504
+ unzip.register(UnzipPassThrough);
13505
+ unzip.register(UnzipInflate);
13506
+ const buffer = [];
13507
+ let haveCheckedSignature = false;
13508
+ for await (const chunk of stream) {
13509
+ if (haveCheckedSignature) {
13510
+ unzip.push(chunk);
13511
+ continue;
13512
+ }
13513
+ buffer.push(chunk);
13514
+ const total = buffer.reduce((s, c) => s + c.length, 0);
13515
+ if (total < 4) continue;
13516
+ haveCheckedSignature = true;
13517
+ const merged = new Uint8Array(total);
13518
+ let off = 0;
13519
+ for (const c of buffer) {
13520
+ merged.set(c, off);
13521
+ off += c.length;
13522
+ }
13523
+ if (!isSignature(merged, ZIP_SIGNATURE)) {
13524
+ for await (const c of stream) buffer.push(c);
13525
+ const fullSize = buffer.reduce((s, c) => s + c.length, 0);
13526
+ const full = new Uint8Array(fullSize);
13527
+ off = 0;
13528
+ for (const c of buffer) {
13529
+ full.set(c, off);
13530
+ off += c.length;
13531
+ }
13532
+ return { ...parseFigData(full), zip_files: void 0 };
13533
+ }
13534
+ unzip.push(merged);
13535
+ }
13536
+ if (!haveCheckedSignature) {
13537
+ if (buffer.length === 0) {
13538
+ throw new Error("readFigFileFromStream: empty stream");
13539
+ }
13540
+ const total = buffer.reduce((s, c) => s + c.length, 0);
13541
+ if (total < 4) {
13542
+ throw new Error("readFigFileFromStream: stream too short to detect format");
13543
+ }
13544
+ const merged = new Uint8Array(total);
13545
+ let off = 0;
13546
+ for (const c of buffer) {
13547
+ merged.set(c, off);
13548
+ off += c.length;
13549
+ }
13550
+ return { ...parseFigData(merged), zip_files: void 0 };
13551
+ }
13552
+ unzip.push(new Uint8Array(0), true);
13553
+ const keys = Object.keys(zipFiles);
13554
+ const mainFile = keys.find((key) => {
13555
+ const fileData = zipFiles[key];
13556
+ if (!fileData || fileData.length <= 8) return false;
13557
+ const prelude = String.fromCharCode.apply(
13558
+ String,
13559
+ Array.from(fileData.slice(0, 8))
13560
+ );
13561
+ return prelude === FIG_KIWI_PRELUDE || prelude === FIGJAM_KIWI_PRELUDE;
13562
+ }) || keys.find((k) => k.endsWith(".fig"));
13563
+ if (!mainFile) {
13564
+ throw new Error(
13565
+ `ZIP archive found but no valid Figma file inside. Files: ${keys.join(", ")}`
13566
+ );
13567
+ }
13568
+ const archiveData = zipFiles[mainFile];
13569
+ return { ...parseFigData(archiveData), zip_files: zipFiles };
13570
+ }
13288
13571
  function parseFigData(data) {
13289
13572
  const { header, files } = FigmaArchiveParser.parseArchive(data);
13290
13573
  const [schemaFile, dataFile, preview] = files;
@@ -14098,6 +14381,8 @@ var iofigma;
14098
14381
  font_size: node.style.fontSize ?? 0,
14099
14382
  font_family: node.style.fontFamily,
14100
14383
  font_weight: node.style.fontWeight ?? 400,
14384
+ font_postscript_name: node.style.fontPostScriptName || void 0,
14385
+ font_style_italic: node.style.italic ?? false,
14101
14386
  font_kerning: true
14102
14387
  };
14103
14388
  }
@@ -14568,8 +14853,19 @@ var iofigma;
14568
14853
  }
14569
14854
  };
14570
14855
  }
14856
+ function findFontMetaDataEntry(fontMetaData, fontName) {
14857
+ if (!fontMetaData?.length || !fontName) return void 0;
14858
+ const match = fontMetaData.find(
14859
+ (m) => m.key?.family === fontName.family && m.key?.style === fontName.style
14860
+ ) ?? fontMetaData[0];
14861
+ return match;
14862
+ }
14571
14863
  function kiwi_text_style_trait(nc) {
14572
14864
  const characters = nc.textData?.characters ?? "";
14865
+ const fontMetaData = nc.derivedTextData?.fontMetaData ?? nc.textData?.fontMetaData;
14866
+ const fontMeta = findFontMetaDataEntry(fontMetaData, nc.fontName);
14867
+ const fontWeight = fontMeta?.fontWeight ?? 400;
14868
+ const italic = fontMeta?.fontStyle === "ITALIC";
14573
14869
  return {
14574
14870
  characters,
14575
14871
  fills: nc.fillPaints ? paints(nc.fillPaints) : [],
@@ -14578,8 +14874,9 @@ var iofigma;
14578
14874
  strokeAlign: nc.strokeAlign ? map.strokeAlign(nc.strokeAlign) : "INSIDE",
14579
14875
  style: {
14580
14876
  fontFamily: nc.fontName?.family ?? "Inter",
14581
- fontPostScriptName: nc.fontName?.postscript,
14582
- fontWeight: 400,
14877
+ fontPostScriptName: nc.fontName?.postscript ? nc.fontName.postscript : void 0,
14878
+ fontWeight,
14879
+ italic,
14583
14880
  fontSize: nc.fontSize ?? 12,
14584
14881
  textAlignHorizontal: nc.textAlignHorizontal ?? "LEFT",
14585
14882
  textAlignVertical: nc.textAlignVertical ?? "TOP",
@@ -14605,7 +14902,8 @@ var iofigma;
14605
14902
  ...kiwi_layout_trait(nc),
14606
14903
  ...kiwi_geometry_trait(nc),
14607
14904
  ...kiwi_corner_trait(nc),
14608
- ...kiwi_effects_trait(nc)
14905
+ ...kiwi_effects_trait(nc),
14906
+ ...kiwi_has_export_settings_trait(nc)
14609
14907
  };
14610
14908
  }
14611
14909
  function ellipse(nc) {
@@ -14616,7 +14914,8 @@ var iofigma;
14616
14914
  ...kiwi_layout_trait(nc),
14617
14915
  ...kiwi_geometry_trait(nc),
14618
14916
  ...kiwi_arc_data_trait(nc),
14619
- ...kiwi_effects_trait(nc)
14917
+ ...kiwi_effects_trait(nc),
14918
+ ...kiwi_has_export_settings_trait(nc)
14620
14919
  };
14621
14920
  }
14622
14921
  function line(nc) {
@@ -14632,6 +14931,7 @@ var iofigma;
14632
14931
  strokeCap: nc.strokeCap ? map.strokeCap(nc.strokeCap) : "NONE",
14633
14932
  strokeJoin: nc.strokeJoin ? map.strokeJoin(nc.strokeJoin) : "MITER",
14634
14933
  strokeMiterAngle: nc.miterLimit,
14934
+ ...kiwi_has_export_settings_trait(nc),
14635
14935
  ...kiwi_effects_trait(nc)
14636
14936
  };
14637
14937
  }
@@ -14642,7 +14942,8 @@ var iofigma;
14642
14942
  ...kiwi_blend_opacity_trait(nc),
14643
14943
  ...kiwi_layout_trait(nc),
14644
14944
  ...kiwi_text_style_trait(nc),
14645
- ...kiwi_effects_trait(nc)
14945
+ ...kiwi_effects_trait(nc),
14946
+ ...kiwi_has_export_settings_trait(nc)
14646
14947
  };
14647
14948
  }
14648
14949
  function isGroupOriginatedFrame(nc) {
@@ -14667,7 +14968,8 @@ var iofigma;
14667
14968
  ...kiwi_children_trait(),
14668
14969
  clipsContent: false,
14669
14970
  fills: [],
14670
- ...kiwi_effects_trait(nc)
14971
+ ...kiwi_effects_trait(nc),
14972
+ ...kiwi_has_export_settings_trait(nc)
14671
14973
  };
14672
14974
  }
14673
14975
  return {
@@ -14719,7 +15021,8 @@ var iofigma;
14719
15021
  ...kiwi_corner_trait(nc),
14720
15022
  ...kiwi_frame_clip_trait(nc),
14721
15023
  ...kiwi_children_trait(),
14722
- ...kiwi_effects_trait(nc)
15024
+ ...kiwi_effects_trait(nc),
15025
+ ...kiwi_has_export_settings_trait(nc)
14723
15026
  };
14724
15027
  const symbolOverrides = nc.symbolData?.symbolOverrides;
14725
15028
  if (Array.isArray(symbolOverrides) && symbolOverrides.length > 0) {
@@ -14769,7 +15072,8 @@ var iofigma;
14769
15072
  ...kiwi_children_trait(),
14770
15073
  clipsContent: false,
14771
15074
  fills: [],
14772
- ...kiwi_effects_trait(nc)
15075
+ ...kiwi_effects_trait(nc),
15076
+ ...kiwi_has_export_settings_trait(nc)
14773
15077
  };
14774
15078
  }
14775
15079
  function windingRule(kiwi3) {
@@ -14796,6 +15100,7 @@ var iofigma;
14796
15100
  ...kiwi_layout_trait(nc),
14797
15101
  ...kiwi_geometry_trait(nc),
14798
15102
  ...kiwi_effects_trait(nc),
15103
+ ...kiwi_has_export_settings_trait(nc),
14799
15104
  cornerRadius: nc.cornerRadius ?? 0,
14800
15105
  vectorNetwork
14801
15106
  };
@@ -14815,7 +15120,8 @@ var iofigma;
14815
15120
  path: "",
14816
15121
  windingRule: path.windingRule ? windingRule(path.windingRule) : "NONZERO"
14817
15122
  })),
14818
- ...kiwi_effects_trait(nc)
15123
+ ...kiwi_effects_trait(nc),
15124
+ ...kiwi_has_export_settings_trait(nc)
14819
15125
  };
14820
15126
  }
14821
15127
  function star(nc) {
@@ -14826,6 +15132,7 @@ var iofigma;
14826
15132
  ...kiwi_layout_trait(nc),
14827
15133
  ...kiwi_geometry_trait(nc),
14828
15134
  ...kiwi_effects_trait(nc),
15135
+ ...kiwi_has_export_settings_trait(nc),
14829
15136
  cornerRadius: nc.cornerRadius ?? 0,
14830
15137
  pointCount: nc.count ?? 5,
14831
15138
  innerRadius: nc.starInnerScale ?? 0.5
@@ -14867,7 +15174,8 @@ var iofigma;
14867
15174
  booleanOperation: nc.booleanOperation ?? "UNION",
14868
15175
  ...kiwi_geometry_trait(nc),
14869
15176
  ...kiwi_children_trait(),
14870
- ...kiwi_effects_trait(nc)
15177
+ ...kiwi_effects_trait(nc),
15178
+ ...kiwi_has_export_settings_trait(nc)
14871
15179
  };
14872
15180
  }
14873
15181
  function regularPolygon(nc) {
@@ -14878,6 +15186,7 @@ var iofigma;
14878
15186
  ...kiwi_layout_trait(nc),
14879
15187
  ...kiwi_geometry_trait(nc),
14880
15188
  ...kiwi_effects_trait(nc),
15189
+ ...kiwi_has_export_settings_trait(nc),
14881
15190
  cornerRadius: nc.cornerRadius ?? 0,
14882
15191
  pointCount: nc.count ?? 3
14883
15192
  };
@@ -15118,6 +15427,18 @@ var iofigma;
15118
15427
  };
15119
15428
  }
15120
15429
  kiwi2.parseFile = parseFile;
15430
+ async function parseFileFromStream(stream, options = {}) {
15431
+ const figData = await readFigFileFromStream(stream);
15432
+ const pages = extractPages(figData, options);
15433
+ return {
15434
+ pages,
15435
+ metadata: {
15436
+ version: figData.header.version
15437
+ },
15438
+ zip_files: figData.zip_files
15439
+ };
15440
+ }
15441
+ kiwi2.parseFileFromStream = parseFileFromStream;
15121
15442
  function extractImages2(zipFiles) {
15122
15443
  return extractImages(zipFiles);
15123
15444
  }
@@ -15841,27 +16162,174 @@ async function ensureFigmaDefaultFonts(canvas) {
15841
16162
  }
15842
16163
 
15843
16164
  // lib.ts
15844
- var FigmaDocument = class {
16165
+ function isFigFileDocument(value) {
16166
+ return value != null && typeof value === "object" && "pages" in value && "metadata" in value && Array.isArray(value.pages);
16167
+ }
16168
+ var _FigmaDocument = class _FigmaDocument {
15845
16169
  /**
15846
- * @param input Raw `.fig` bytes (Uint8Array) or a parsed Figma REST API
15847
- * document JSON object.
16170
+ * @param input Raw `.fig` bytes (Uint8Array), a pre-parsed FigFileDocument
16171
+ * (e.g. from parseFileFromStream for large files), or REST JSON.
15848
16172
  *
15849
16173
  * For file-path convenience in Node, use `FigmaDocument.fromFile()` from
15850
16174
  * the `@grida/refig` entrypoint.
15851
16175
  */
15852
16176
  constructor(input) {
16177
+ /**
16178
+ * Cache of ResolvedScene per rootNodeId. REST with images is not cached.
16179
+ * Bounded to avoid unbounded memory growth in --export-all flows.
16180
+ */
16181
+ this._sceneCache = /* @__PURE__ */ new Map();
15853
16182
  if (input instanceof Uint8Array) {
15854
16183
  this.sourceType = "fig-file";
15855
16184
  this.payload = input;
15856
16185
  return;
15857
16186
  }
16187
+ if (isFigFileDocument(input)) {
16188
+ this.sourceType = "fig-file";
16189
+ this.payload = input;
16190
+ this._figFile = input;
16191
+ return;
16192
+ }
15858
16193
  if (!input || typeof input !== "object" || Array.isArray(input)) {
15859
- throw new Error("FigmaDocument: input must be a Uint8Array or object");
16194
+ throw new Error(
16195
+ "FigmaDocument: input must be a Uint8Array, FigFileDocument, or REST JSON object"
16196
+ );
15860
16197
  }
15861
16198
  this.sourceType = "rest-api-json";
15862
16199
  this.payload = input;
15863
16200
  }
16201
+ /**
16202
+ * Resolve document to Grida IR (scene JSON + images to register).
16203
+ * On-demand, cached when deterministic (no images for REST).
16204
+ * Cache is bounded (LRU eviction) to avoid OOM in --export-all flows.
16205
+ * @internal
16206
+ */
16207
+ _resolve(rootNodeId, images) {
16208
+ const cacheKey = rootNodeId ?? "";
16209
+ if (this.sourceType === "fig-file") {
16210
+ const cached = this._sceneCacheGet(cacheKey);
16211
+ if (cached) return cached;
16212
+ const resolved = this._figToScene(rootNodeId);
16213
+ this._sceneCacheSet(cacheKey, resolved);
16214
+ return resolved;
16215
+ }
16216
+ if (images == null || Object.keys(images).length === 0) {
16217
+ const cached = this._sceneCacheGet(cacheKey);
16218
+ if (cached) return cached;
16219
+ const resolved = this._restToScene(rootNodeId);
16220
+ this._sceneCacheSet(cacheKey, resolved);
16221
+ return resolved;
16222
+ }
16223
+ return this._restToScene(rootNodeId, images);
16224
+ }
16225
+ _sceneCacheGet(key) {
16226
+ const v = this._sceneCache.get(key);
16227
+ if (v === void 0) return void 0;
16228
+ this._sceneCache.delete(key);
16229
+ this._sceneCache.set(key, v);
16230
+ return v;
16231
+ }
16232
+ _sceneCacheSet(key, value) {
16233
+ if (this._sceneCache.size >= _FigmaDocument._MAX_SCENE_CACHE) {
16234
+ const firstKey = this._sceneCache.keys().next().value;
16235
+ if (firstKey !== void 0) this._sceneCache.delete(firstKey);
16236
+ }
16237
+ this._sceneCache.set(key, value);
16238
+ }
16239
+ /** @internal */
16240
+ _restToScene(rootNodeId, images) {
16241
+ const result = restJsonToSceneJson(
16242
+ this.payload,
16243
+ rootNodeId,
16244
+ images
16245
+ );
16246
+ if (images && Object.keys(images).length > 0) {
16247
+ const imagesToRegister = {};
16248
+ for (const ref of result.imageRefsUsed) {
16249
+ if (ref in images) {
16250
+ imagesToRegister[ref] = images[ref];
16251
+ }
16252
+ }
16253
+ return {
16254
+ sceneJson: result.sceneJson,
16255
+ images: imagesToRegister,
16256
+ imageRefsUsed: result.imageRefsUsed
16257
+ };
16258
+ }
16259
+ return {
16260
+ sceneJson: result.sceneJson,
16261
+ images: {},
16262
+ imageRefsUsed: result.imageRefsUsed
16263
+ };
16264
+ }
16265
+ /** @internal */
16266
+ _figToScene(rootNodeId) {
16267
+ if (!this._figFile) {
16268
+ this._figFile = iofigma.kiwi.parseFile(this.payload);
16269
+ }
16270
+ return figFileToSceneJson(this._figFile, rootNodeId);
16271
+ }
16272
+ /**
16273
+ * Returns the list of font family names used in this document.
16274
+ *
16275
+ * The result is family names only — no weights, PostScript names, or other metadata.
16276
+ * That is intentional: in practice, you can load all TTF/OTF files for each family
16277
+ * (variable or static) and pass them to the renderer; it will resolve the correct face
16278
+ * per text style. We prioritize simple usage and accurate font selection over
16279
+ * performance or resource-optimized patterns.
16280
+ *
16281
+ * When rootNodeId is omitted, traverses all pages so fonts from every page are included.
16282
+ *
16283
+ * @param rootNodeId — Optional. When provided, scope to that node's subtree. Omit for the full document (all pages).
16284
+ * @returns Unique font family names (e.g. `["Inter", "Caveat", "Roboto"]`).
16285
+ */
16286
+ listFontFamilies(rootNodeId) {
16287
+ if (rootNodeId != null && rootNodeId !== "") {
16288
+ const resolved = this._resolve(rootNodeId);
16289
+ return this._collectFontFamiliesFromSceneJson(resolved.sceneJson);
16290
+ }
16291
+ if (this.sourceType === "rest-api-json") {
16292
+ return collectFontFamiliesFromRestDocument(
16293
+ this.payload
16294
+ );
16295
+ }
16296
+ if (!this._figFile) {
16297
+ this._figFile = iofigma.kiwi.parseFile(this.payload);
16298
+ }
16299
+ const figFile = this._figFile;
16300
+ const pages = figFile.pages;
16301
+ if (!pages?.length) return [];
16302
+ const sortedPages = [...pages].sort(
16303
+ (a, b) => a.sortkey.localeCompare(b.sortkey)
16304
+ );
16305
+ const families = /* @__PURE__ */ new Set();
16306
+ for (let i = 0; i < sortedPages.length; i++) {
16307
+ const resolved = figFileToSceneJson(figFile, void 0, i);
16308
+ for (const f of this._collectFontFamiliesFromSceneJson(
16309
+ resolved.sceneJson
16310
+ )) {
16311
+ families.add(f);
16312
+ }
16313
+ }
16314
+ return Array.from(families);
16315
+ }
16316
+ _collectFontFamiliesFromSceneJson(sceneJson) {
16317
+ const parsed = JSON.parse(sceneJson);
16318
+ const nodes = parsed?.document?.nodes;
16319
+ if (!nodes || typeof nodes !== "object") return [];
16320
+ const families = /* @__PURE__ */ new Set();
16321
+ for (const node of Object.values(nodes)) {
16322
+ const family = node.font_family;
16323
+ if (typeof family === "string" && family) {
16324
+ families.add(family);
16325
+ }
16326
+ }
16327
+ return Array.from(families);
16328
+ }
15864
16329
  };
16330
+ /** Max cached scenes; evicts LRU when exceeded. */
16331
+ _FigmaDocument._MAX_SCENE_CACHE = 64;
16332
+ var FigmaDocument = _FigmaDocument;
15865
16333
  function resolveMimeType(format) {
15866
16334
  const map = {
15867
16335
  png: "image/png",
@@ -15968,6 +16436,28 @@ function exportSettingToRenderOptions(node, setting) {
15968
16436
  }
15969
16437
  return { format, width: DEFAULT_EXPORT_SIZE, height: DEFAULT_EXPORT_SIZE };
15970
16438
  }
16439
+ function collectFontFamiliesFromRestDocument(json) {
16440
+ const families = /* @__PURE__ */ new Set();
16441
+ const doc = json;
16442
+ const pages = doc?.document?.children;
16443
+ if (!pages?.length) return [];
16444
+ function walk(nodes) {
16445
+ for (const node of nodes) {
16446
+ const style = node.style;
16447
+ const family = style?.fontFamily;
16448
+ if (typeof family === "string" && family) {
16449
+ families.add(family);
16450
+ }
16451
+ const children = node.children;
16452
+ if (children?.length) walk(children);
16453
+ }
16454
+ }
16455
+ for (const page of pages) {
16456
+ const pageChildren = page.children;
16457
+ if (pageChildren?.length) walk(pageChildren);
16458
+ }
16459
+ return Array.from(families);
16460
+ }
15971
16461
  function findNodeInRestDocument(json, nodeId) {
15972
16462
  const doc = json;
15973
16463
  const pages = doc?.document?.children;
@@ -16134,8 +16624,7 @@ function figFileToRestLikeDocument(figFile) {
16134
16624
  }
16135
16625
  };
16136
16626
  }
16137
- function figBytesToSceneJson(figBytes, rootNodeId) {
16138
- const figFile = iofigma.kiwi.parseFile(figBytes);
16627
+ function figFileToSceneJson(figFile, rootNodeId, pageIndex) {
16139
16628
  const pages = figFile.pages;
16140
16629
  if (!pages || pages.length === 0) {
16141
16630
  throw new Error("FigmaDocument: .fig file has no pages");
@@ -16152,6 +16641,8 @@ function figBytesToSceneJson(figBytes, rootNodeId) {
16152
16641
  );
16153
16642
  }
16154
16643
  page = pageWithNode;
16644
+ } else if (pageIndex != null && pageIndex >= 0 && pageIndex < sortedPages.length) {
16645
+ page = sortedPages[pageIndex];
16155
16646
  } else {
16156
16647
  page = sortedPages[0];
16157
16648
  }
@@ -16201,38 +16692,26 @@ var FigmaRenderer = class {
16201
16692
  this._canvas
16202
16693
  );
16203
16694
  }
16695
+ if (this.options.fonts) {
16696
+ for (const [family, data] of Object.entries(this.options.fonts)) {
16697
+ const entries = Array.isArray(data) ? data : [data];
16698
+ for (const bytes of entries) {
16699
+ this._canvas.addFont(family, bytes);
16700
+ }
16701
+ }
16702
+ }
16204
16703
  return this._canvas;
16205
16704
  }
16206
16705
  loadScene(canvas, nodeId) {
16207
16706
  if (this._sceneLoaded && this._requestedNodeId === nodeId) return;
16208
- let sceneJson;
16209
- let imagesToRegister = {};
16210
- if (this.document.sourceType === "fig-file") {
16211
- const figResult = figBytesToSceneJson(
16212
- this.document.payload,
16213
- nodeId || void 0
16214
- );
16215
- sceneJson = figResult.sceneJson;
16216
- imagesToRegister = figResult.images;
16217
- } else {
16218
- const restResult = restJsonToSceneJson(
16219
- this.document.payload,
16220
- nodeId || void 0,
16221
- this.options.images
16222
- );
16223
- sceneJson = restResult.sceneJson;
16224
- const used = new Set(restResult.imageRefsUsed);
16225
- const provided = this.options.images ?? {};
16226
- for (const ref of used) {
16227
- if (ref in provided) {
16228
- imagesToRegister[ref] = provided[ref];
16229
- }
16230
- }
16231
- }
16232
- for (const [ref, bytes] of Object.entries(imagesToRegister)) {
16707
+ const resolved = this.document._resolve(
16708
+ nodeId || void 0,
16709
+ this.options.images
16710
+ );
16711
+ for (const [ref, bytes] of Object.entries(resolved.images)) {
16233
16712
  canvas.addImageWithId(bytes, `res://images/${ref}`);
16234
16713
  }
16235
- canvas.loadScene(sceneJson);
16714
+ canvas.loadScene(resolved.sceneJson);
16236
16715
  this._requestedNodeId = nodeId;
16237
16716
  this._sceneLoaded = true;
16238
16717
  }