@lark-apaas/coding-steering 0.1.18-dev.0450c3c → 0.1.18-dev.12ae0d8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/coding-steering",
3
- "version": "0.1.18-dev.0450c3c",
3
+ "version": "0.1.18-dev.12ae0d8",
4
4
  "description": "Stack-specific steering content for miaoda-coding templates",
5
5
  "type": "module",
6
6
  "files": [
@@ -28,6 +28,7 @@ OPAQUE_ENOUGH = 128 # 能当背景的最低不透明度:低于半透明就
28
28
  # 那是叠加装饰不是背景
29
29
  FILL_MANY = 5 # 「被大量当填充铺开」的次数下限,用于区分卡片底与偶发用色
30
30
  BG_CONTENT_CAP = 5 # 内容页背景收几张:再多消费端也挑不过来,超出的写进 TODO 交人取舍
31
+ SHEET_CAP = 12 # 联系表展示上限;进包的资产不受它约束,一张都不截
31
32
 
32
33
  HERE = os.path.dirname(os.path.abspath(__file__))
33
34
  SKILL_ROOT = os.path.dirname(HERE)
@@ -913,6 +914,22 @@ def top_bleed_media(shapes):
913
914
  return out
914
915
 
915
916
 
917
+ def slot_overlaps(slots):
918
+ """同一页型里坐标互相重叠的槽对。只报事实,不改坐标——坐标是从模板量的。"""
919
+ out = []
920
+ for i in range(len(slots)):
921
+ for j in range(i + 1, len(slots)):
922
+ a, b = slots[i].get('box'), slots[j].get('box')
923
+ if not (a and b):
924
+ continue
925
+ ox = min(a[0] + a[2], b[0] + b[2]) - max(a[0], b[0])
926
+ oy = min(a[1] + a[3], b[1] + b[3]) - max(a[1], b[1])
927
+ if ox > 0 and oy > 0:
928
+ out.append('%s×%s 叠 %dx%d' % (slots[i].get('role'), slots[j].get('role'),
929
+ round(ox), round(oy)))
930
+ return out
931
+
932
+
916
933
  def slot_style(s):
917
934
  """占位符自带的排版样式——字号/色值/对齐/字重都是直读,不给消费端留编的空间。
918
935
 
@@ -1356,7 +1373,7 @@ def contact_sheet(outdir, cands, path):
1356
1373
  except Exception:
1357
1374
  return None
1358
1375
  cell, pad, cols = 220, 20, 4
1359
- items = cands[:12]
1376
+ items = cands # 上限由调用方定,编号与 BRIEF 表格一一对应
1360
1377
  if not items:
1361
1378
  return None
1362
1379
  rows = (len(items) + cols - 1) // cols
@@ -1699,6 +1716,13 @@ def emit_layouts(archetypes, ldir, busy_hints=None, facts=None, recipes=None):
1699
1716
  % '、'.join('%s=%d字' % (b, n) for b, n in fx['chars'][:6]))
1700
1717
  if fx.get('recipes'):
1701
1718
  L.append(' # 命中配方:%s' % '、'.join(fx['recipes'][:4]))
1719
+ # 槽与槽在坐标上重叠:PPT 里占位符互相压是常态(文字 valign 居中、样张只有一行,
1720
+ # 看不出来),照抄坐标做成 HTML 后内容一变长就撞。实测封面 title 框比 subtitle
1721
+ # 的顶还低 41px,两行标题直接压在副标题上。这里只报事实,怎么让开由你定。
1722
+ ov = slot_overlaps(a.get('slots') or [])
1723
+ if ov:
1724
+ L.append(' # 槽位重叠:%s(模板里靠文字居中不显形,内容变长会撞)'
1725
+ % '、'.join(ov[:3]))
1702
1726
  L.append(' %s:' % a['name'])
1703
1727
  if a.get('role'):
1704
1728
  L.append(' role: %s' % a['role'])
@@ -1864,7 +1888,7 @@ def emit_body(d, tokens, fonts, roles, assets, archetypes, exceptions, cusage, l
1864
1888
 
1865
1889
  def emit_brief(d, ctx, ldir):
1866
1890
  (tokens, rest, fonts, roles, assets, rejected, todos, archetypes, cands, sheet,
1867
- leftover, lsheet) = ctx
1891
+ leftover, lsheet, sheet_n) = ctx
1868
1892
  canvas = d['canvas']['px']
1869
1893
  L = ['# 抽取简报(第 1/3 步产物;改完草案跑 package.py 出包)', '',
1870
1894
  '源:`%s` 画布 %dx%d %d 页 / %d 版式 主题 %s form=%s'
@@ -1901,16 +1925,20 @@ def emit_brief(d, ctx, ldir):
1901
1925
  for t in todos:
1902
1926
  L.append('- ' + t)
1903
1927
  L += ['', '## 联系表(一次看完所有候选图)', '',
1904
- '`l-out/contact-sheet.png` —— 编号对应下表;看完再决定 logo / 封面归属。' if sheet
1928
+ '`l-out/contact-sheet.png` —— 图格编号对应下表前几行;看完再决定 logo / 封面归属。' if sheet
1905
1929
  else '(Pillow 不可用,未生成联系表;逐张看 `media-out/`)', '',
1906
1930
  '| # | 文件 | 尺寸 | 出现 | 满屏 | 页 | 草案判定 |', '|---|---|---|---|---|---|---|']
1907
1931
  decided = {a['src']['file']: a['id'] for a in assets}
1908
1932
  why = {c['file']: r for c, r in rejected}
1909
- for i, c in enumerate(cands[:12], 1):
1933
+ for i, c in enumerate(cands, 1):
1910
1934
  L.append('| %d | `%s` | %sx%s | %d | %s | %s | %s |' % (
1911
1935
  i, c['file'], c['probe'].get('w') or '?', c['probe'].get('h') or '?', c['n'],
1912
1936
  'Y' if c['fullscreen'] else '', ','.join(map(str, c['slides'][:6])) or 'layout',
1913
1937
  decided.get(c['file']) or ('✗ ' + why.get(c['file'], '未采纳'))))
1938
+ if len(cands) > sheet_n:
1939
+ L.append('')
1940
+ L.append('拼版图只含前 %d 张(第 %d 行之后的没有图格)。要看后面某张,'
1941
+ '按文件名直接看 `media-out/`。' % (sheet_n, sheet_n))
1914
1942
  L += ['', '## 颜色(草案 token 已写进 frontmatter.yaml)', '',
1915
1943
  '| token | hex | 出现 |', '|---|---|---|']
1916
1944
  for name, r in tokens:
@@ -1962,7 +1990,15 @@ def main(argv=None):
1962
1990
  tokens, rest, rows = draft_colors(d, cusage)
1963
1991
  fonts = draft_fonts(d)
1964
1992
  archetypes, pages, leftover = draft_layouts(d, outdir)
1993
+ # 封面底图:form=3 的页型键就是角色名(cover/section/...),直接按名字取。
1994
+ # form=2 按样张聚类,键是 layout-1..N,永远匹配不上 'cover'——实测 vo-lite 因此
1995
+ # 一张 role: cover 都没有,封面主视觉被标成 bg-content-1,消费端拿不到封面资产,
1996
+ # design.md 的「封面底图必用 cover 资产」这条硬规则无从满足。回退到覆盖第 1 页的
1997
+ # 那个页型:deck 的第 1 页就是封面,这是版式无关的事实。
1965
1998
  cover_media = next((a['bg_raw'] for a in archetypes if a['name'] == 'cover'), None)
1999
+ if not cover_media:
2000
+ cover_media = next((a['bg_raw'] for a in archetypes
2001
+ if 1 in (a.get('pages') or ())), None)
1966
2002
  bg_needed = {a['bg_raw'] for a in archetypes if a['bg_raw'] and a['bg_raw'].startswith('ppt/media')}
1967
2003
  bg_under = {p['no']: p['bg_media'] for p in pages}
1968
2004
  assets, rejected, todos, alias, pool = draft_assets(d, outdir, bg_needed, cover_media, bg_under)
@@ -2018,9 +2054,23 @@ def main(argv=None):
2018
2054
  a['slots'] = keep
2019
2055
  roles = draft_scale(d, archetypes)
2020
2056
  slot_added = cover_slot_colors(tokens, archetypes, rows, cusage)
2021
- cands = sorted([c for c in [a['src'] for a in assets]] +
2022
- [c for c, _ in rejected], key=lambda c: (-c['n'], c['file']))
2023
- sheet = contact_sheet(outdir, cands, os.path.join(ldir, 'contact-sheet.png'))
2057
+ # 进包的资产必须全部上联系表。BRIEF L 层「看联系表确认 logo / 封面归属」,
2058
+ # 表上没有的东西它只会从表里另挑一张顶上去。封面主视觉按定义只出现在封面那一页
2059
+ # (n=1),按出现次数排序时排在最末——实测被 cands[:12] 截掉,模型于是把 bg-cover
2060
+ # 换成了已经在用的内容页背景,封面与内容页字节相同,封面主视觉整个丢失。
2061
+ decided_c = sorted([a['src'] for a in assets], key=lambda c: (-c['n'], c['file']))
2062
+ other_c = sorted([c for c, _ in rejected], key=lambda c: (-c['n'], c['file']))
2063
+ cands, seen_file = [], set()
2064
+ for c in decided_c + other_c: # 同一张图可能有多条候选记录(不同位置各一条)
2065
+ if c['file'] not in seen_file:
2066
+ seen_file.add(c['file'])
2067
+ cands.append(c)
2068
+ # 表列全部候选,拼版图只拼前几张:两者成本差着数量级。表是文字,60 行也几乎不占
2069
+ # 上下文,却是模型唯一能知道「存在这张图」的地方——名额砍在这里,被误判成未采纳的
2070
+ # 图连翻案的机会都没有。拼版图是要「看」的,60 格就是 4 列×15 行、降采样后每格
2071
+ # 糊成一团,那个上限才有意义。
2072
+ sheet_items = cands[:max(SHEET_CAP, len(decided_c))]
2073
+ sheet = contact_sheet(outdir, sheet_items, os.path.join(ldir, 'contact-sheet.png'))
2024
2074
  lsheet = layout_sheet(outdir, archetypes, os.path.join(ldir, 'layout-sheet.png'))
2025
2075
 
2026
2076
  anchors = draft_anchors(d, tokens, fonts, roles, assets, archetypes)
@@ -2091,7 +2141,7 @@ def main(argv=None):
2091
2141
  emit_layouts(archetypes, ldir, busy_hints, facts, recipes)
2092
2142
  emit_body(d, tokens, fonts, roles, assets, archetypes, exceptions, cusage, ldir)
2093
2143
  emit_brief(d, (tokens, rest, fonts, roles, assets, rejected, todos, archetypes, cands, sheet,
2094
- leftover, lsheet), ldir)
2144
+ leftover, lsheet, len(sheet_items)), ldir)
2095
2145
 
2096
2146
  # 这几行落在模型判断「skill 是不是做完了」的那一刻。只报数就会被读成「包已生成」,
2097
2147
  # 于是判断和打包整段被跳过,deck 拿不到任何版式坐标。所以这里报进度与下一条命令。
@@ -274,6 +274,10 @@ def truthy(v):
274
274
  return str(v).strip().lower() in ('1', 'true', 'yes', 'on')
275
275
 
276
276
 
277
+ # 浏览器能解码的图片格式。落进包的资产必须在此列,否则消费端引用到就是一张空白图。
278
+ WEB_SAFE_EXT = {'png', 'jpg', 'jpeg', 'webp', 'gif', 'svg', 'avif'}
279
+
280
+
277
281
  def asset_ext(name):
278
282
  return name.rsplit('.', 1)[-1].lower() if '.' in name else ''
279
283
 
@@ -353,12 +357,25 @@ def place_assets(manifest, extract, stage1, pack):
353
357
  entry['path'] = 'assets/%ss/%s.%s' % (kind, base, cext)
354
358
  if truthy(a.get('use_full')):
355
359
  oext = asset_ext(row['out'])
356
- fdst = os.path.join(sub, '%s@full.%s' % (base, oext))
357
- shutil.copy2(orig_path, fdst)
358
- copied.append(fdst)
359
- entry['full'] = 'assets/%ss/%s@full.%s' % (kind, base, oext)
360
+ if oext.lower() in WEB_SAFE_EXT:
361
+ fdst = os.path.join(sub, '%s@full.%s' % (base, oext))
362
+ shutil.copy2(orig_path, fdst)
363
+ copied.append(fdst)
364
+ entry['full'] = 'assets/%ss/%s@full.%s' % (kind, base, oext)
365
+ else:
366
+ # 原图是浏览器不解码的格式(tiff/bmp 之类)。落进包并在 design.md
367
+ # 里声明成可用资源,消费端引用它就是一张空白图——实测踩过一次,
368
+ # 封面与结束页因此空白。压缩版已经带着全部像素,full 不给。
369
+ print(' ⚠ %s 的原图是 .%s,浏览器不解码,只给压缩版' % (aid, oext))
360
370
  else:
361
371
  oext = asset_ext(row['out'])
372
+ if oext.lower() not in WEB_SAFE_EXT:
373
+ # 转码两条路(Pillow / sips)都没成,原图又是浏览器不解码的格式。
374
+ # 照落进去就是把一张永远显示不出来的图当资产下发——实测封面因此空白。
375
+ raise Fail('%s: 原图是 .%s,浏览器不解码,而转码没有产物'
376
+ '(extract.json 里看 transcode_blocked 的原因)。'
377
+ '装上 Pillow 重跑抽取,或把这条资产从 manifest 删掉'
378
+ '并在 gaps 写明。' % (aid, oext))
362
379
  dst = os.path.join(sub, '%s.%s' % (base, oext))
363
380
  shutil.copy2(orig_path, dst)
364
381
  copied.append(dst)