mirror of
https://github.com/PR0M3TH3AN/Artstr-Studio.git
synced 2026-11-02 03:30:39 +00:00
book(C4): text-frame layer kind (structural rectangle on masters)
A new `text-frame` shape kind lets masters carry a reflow target —
the rectangle the Phase C.5 pagination engine will pour markdown
into.
- `defaultShapeData('text-frame')` returns the frame defaults:
padding 0.05 in, verticalAlign 'top', columns 1, styleOverrides
null. Matches the data-model spec in
docs/BOOK_DESIGNER_FEATURE.md.
- `buildShapeSvgInner` renders the kind as a dashed accent-blue
rectangle with a centered "Text frame" caption — non-rendering
in the final print output (transparent fill / dashed stroke);
the dashed outline is a positioning aid in the editor and gets
hidden by the engine once prose flows in.
- New "+ Text frame" button on the page-context bar, shown only
when editing a master (`editing-book-master` body class). Drops
a frame sized to the trim minus a 0.75 in margin so it reads as
a real "type area" out of the box; the user can resize / move
it like any other shape with the existing transform handles.
- `addBookTextFrameToActiveMaster()` snaps coordinates to the
canvas space (trim + bleed) so the frame lands inside the
printable area regardless of bleed.
Phase C4 ships the structural layer only — the frame doesn't yet
collect markdown. Phase C.5 vendors `marked`, adds the pagination
engine, and starts pouring chapter prose into these frames.
This commit is contained in:
1 parent
f28f0d92e8
commit
4e75db99b8
1 file changed
+59
-1
+59
-1
@@ -4416,6 +4416,7 @@
|
||||
<button type="button" id="bookPageEditPrev" title="Previous page">←</button>
|
||||
<button type="button" id="bookPageEditNext" title="Next page">→</button>
|
||||
<span id="bookPageEditLabel"></span>
|
||||
<button type="button" id="bookMasterAddTextFrame" title="Add a reflow text-frame to this master" style="display:none">+ Text frame</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8409,6 +8410,41 @@
|
||||
syncInputs();
|
||||
render();
|
||||
}
|
||||
// Add a text-frame layer (Phase C4 structural placeholder) to
|
||||
// the master currently open in the editor. Sized to fill the
|
||||
// trim with a small inset so it reads as a "type area" out of
|
||||
// the box; the user can resize / move like any other shape.
|
||||
function addBookTextFrameToActiveMaster() {
|
||||
const book = state.book;
|
||||
if (!book || !book.editingMasterId) return;
|
||||
const target = `book-master:${book.editingMasterId}`;
|
||||
const s = book.spec;
|
||||
const trimW = Number(s.trimW) || 6;
|
||||
const trimH = Number(s.trimH) || 9;
|
||||
const bleed = Number(s.bleed) || 0;
|
||||
// Type area = trim minus a 0.75 in margin all around (a
|
||||
// reasonable book-page default; the user can drag it tighter
|
||||
// or wider). Coordinates are inches in the canvas space; the
|
||||
// canvas covers (trim + 2 × bleed) in each axis.
|
||||
const margin = 0.75;
|
||||
const x = bleed + margin;
|
||||
const y = bleed + margin;
|
||||
const w = Math.max(0.5, trimW - margin * 2);
|
||||
const h = Math.max(0.5, trimH - margin * 2);
|
||||
pushHistory('book: add text frame');
|
||||
addLayer({
|
||||
id: makeLayerId(),
|
||||
type: 'shape',
|
||||
name: 'Text frame',
|
||||
target,
|
||||
x, y, w, h,
|
||||
rotate: 0, opacity: 1, z: nextLayerZ(),
|
||||
shape: defaultShapeData('text-frame'),
|
||||
fill: { type: 'none' },
|
||||
stroke: { type: 'none', color: '#60a5fa', width: 0, dash: 'solid' },
|
||||
});
|
||||
}
|
||||
|
||||
function setBookDefaultMaster(id) {
|
||||
const book = state.book;
|
||||
if (!book) return;
|
||||
@@ -8861,17 +8897,20 @@
|
||||
const editingMaster = inBook && !!book?.editingMasterId;
|
||||
bar.style.display = (editingPage || editingMaster) ? '' : 'none';
|
||||
if (!editingPage && !editingMaster) return;
|
||||
const tfBtn = document.getElementById('bookMasterAddTextFrame');
|
||||
if (editingMaster) {
|
||||
const m = book.masters.find(x => x.id === book.editingMasterId);
|
||||
if (label) label.textContent = `Master: ${m?.label || 'Untitled'}`;
|
||||
if (back) back.textContent = '← Back to masters';
|
||||
if (prev) prev.style.display = 'none';
|
||||
if (next) next.style.display = 'none';
|
||||
if (tfBtn) tfBtn.style.display = '';
|
||||
return;
|
||||
}
|
||||
// editingPage path
|
||||
if (prev) prev.style.display = '';
|
||||
if (next) next.style.display = '';
|
||||
if (tfBtn) tfBtn.style.display = 'none';
|
||||
if (back) back.textContent = '← Back to Pages';
|
||||
const designed = book.pages.filter(p => p.type === 'design');
|
||||
const idx = designed.findIndex(p => p.id === book.editingEntryId);
|
||||
@@ -17082,10 +17121,19 @@
|
||||
if (kind === 'star') { out.points = 5; out.innerRadiusRatio = 0.45; }
|
||||
if (kind === 'line') { out.x1 = 0; out.y1 = 50; out.x2 = 100; out.y2 = 50; out.strokeWidth = 6; }
|
||||
if (kind === 'path') {
|
||||
// Default heart shape so a fresh path layer is immediately useful.
|
||||
out.d = 'M 50 90 C 0 60 0 20 30 10 C 40 5 50 20 50 30 C 50 20 60 5 70 10 C 100 20 100 60 50 90 Z';
|
||||
out.viewBox = { x: 0, y: 0, w: 100, h: 100 };
|
||||
}
|
||||
if (kind === 'text-frame') {
|
||||
// Phase C4 — structural layer only. The pagination engine
|
||||
// in C.5 pours markdown into this frame; until then it
|
||||
// renders as a labelled dashed rectangle on the master so
|
||||
// users can position + size it like any other shape.
|
||||
out.padding = 0.05;
|
||||
out.verticalAlign = 'top';
|
||||
out.columns = 1;
|
||||
out.styleOverrides = null;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -17531,6 +17579,15 @@
|
||||
let body = '';
|
||||
if (kind === 'rect') {
|
||||
body = `<rect x="0" y="0" width="100" height="${H}" fill="${fillAttr}"${strokeAttr}/>`;
|
||||
} else if (kind === 'text-frame') {
|
||||
// Phase C4 structural rectangle. Dashed accent outline +
|
||||
// centered "Text frame" label; transparent fill. The
|
||||
// pagination engine in C.5 will replace this render path
|
||||
// for frames that have markdown poured into them — at that
|
||||
// point the dashed outline only renders in the editor.
|
||||
const dash = Math.max(0.8, Math.min(4, 100 / 40));
|
||||
body = `<rect x="0" y="0" width="100" height="${H}" fill="transparent" stroke="#60a5fa" stroke-width="0.5" stroke-dasharray="${dash} ${dash}"/>`
|
||||
+ `<text x="50" y="${(H / 2).toFixed(2)}" text-anchor="middle" dominant-baseline="middle" font-family="ui-sans-serif, system-ui, sans-serif" font-size="${Math.min(6, H / 8).toFixed(2)}" fill="#60a5fa" opacity="0.7">Text frame</text>`;
|
||||
} else if (kind === 'rounded-rect') {
|
||||
const r = Math.max(0, Math.min(50, Number(shape.cornerRadius) || 0));
|
||||
body = `<rect x="0" y="0" width="100" height="${H}" rx="${r}" ry="${r}" fill="${fillAttr}"${strokeAttr}/>`;
|
||||
@@ -32011,6 +32068,7 @@
|
||||
});
|
||||
on('bookPageEditPrev', 'click', () => gotoBookPageRelative(-1));
|
||||
on('bookPageEditNext', 'click', () => gotoBookPageRelative(1));
|
||||
on('bookMasterAddTextFrame', 'click', () => addBookTextFrameToActiveMaster());
|
||||
|
||||
// Book Designer — Masters modal.
|
||||
on('bookManageMastersBtn', 'click', openBookMastersModal);
|
||||
|
||||
Reference in new issue
Block a user