From: Thomas Walker Lynch Date: Fri, 7 Aug 2026 07:29:10 +0000 (+0000) Subject: code ontinuations working, theme width can be closed X-Git-Url: https://git.reasoningtechnology.com/%27%20%20%20window.RT.dirpr_library%20%20%20%27/%27%20%20%20key%20%20%20%27?a=commitdiff_plain;h=4d11bed6214ed472217b526d7d862b77a83db948;p=RT-Style code ontinuations working, theme width can be closed --- diff --git a/developer/authored/Manuscript.copy/Document/design.html b/developer/authored/Manuscript.copy/Document/design.html index d366957..1c6ca8f 100644 --- a/developer/authored/Manuscript.copy/Document/design.html +++ b/developer/authored/Manuscript.copy/Document/design.html @@ -862,6 +862,28 @@

+ + Code that overruns the measure +

+ Code is set in a fixed pitch face and does not wrap, so a line longer than the measure runs off the right edge and is lost. Four responses are available and only two survive contact with print. +

+

+ Reducing the face until the widest line fits lets one long line dictate the size of every block on the page, and changes heights, which feeds back into pagination. Scrolling the block works on a screen and fails on paper, and fails silently, which is the property this engine works hardest to avoid. Having the author shorten the line gives the best result of all, since no engine chooses a break as well as the person who wrote the code, but it is not something the engine can do. Wrapping with a continuation mark works identically on screen and on paper and loses nothing. +

+

+ The default is therefore to wrap, following the compositor's convention: mark the broken line so the reader knows it continues, and set the carried portion flush right. Indentation carries much of the meaning in a listing, and a carried fragment beginning at the left margin reads as a statement in its own right. Pushed to the right margin it cannot be mistaken for one. +

+

+ A fixed pitch face makes the arithmetic exact rather than a search: one character width divides into the measure and gives the columns available. +

+

+ An overflow is reported as well as repaired. A line long enough to need carrying is usually a line its author would rather rewrite, and a wrapped listing is a compromise even when it is a correct one. The report names the columns available, the longest line, and the first line to overflow, so the source can be found without hunting. This is what turns the best strategy — the author fixing the line — from something noticed by eye into a worklist, and it gives the same guarantee to whatever renders the document to paper. +

+

+ A fixed pitch face also reads larger than a proportional one at the same size, since its widest glyphs set the measure. The tradition is a point or two smaller, eleven against twelve or sometimes ten. One correction is made for the face and a second for the impression. +

+
+ Widows and orphans

diff --git a/developer/authored/Manuscript.copy/Element/code.js b/developer/authored/Manuscript.copy/Element/code.js index a2cd26d..e9c2394 100644 --- a/developer/authored/Manuscript.copy/Element/code.js +++ b/developer/authored/Manuscript.copy/Element/code.js @@ -31,6 +31,69 @@ } }; + /* Wrapping an overflowing code line. + + Code is set in a fixed pitch face with white-space: pre ,so it does not + wrap and a long line runs off the right edge and is lost. Three responses + were considered. Shrinking the face to fit lets one long line dictate the + size of every block on the page. Scrolling works on a screen and fails on + paper ,and fails silently ,which is the property worth avoiding above all. + Wrapping with a mark works the same in both and loses nothing. + + The convention is the compositor's: mark the broken line so the reader + knows it continues ,and set the carried portion flush right so it reads as + a continuation rather than as a new line of code. Indentation carries most + of the meaning in a listing ,and a carried fragment starting at the left + margin would read as a statement in its own right. Pushed to the right + margin it cannot be mistaken for one. + + A fixed pitch face makes the arithmetic exact: one character width divides + into the measure and gives the columns available ,with no search needed. + */ + /* The cue is a pair. A mark at the end of a broken line says the line goes + on; a mark at the start of the carried fragment says this is where it went. + One alone leaves the reader to infer the other end. */ + const BREAKS = '\u21A9'; // leftwards arrow with hook: continues + const RESUMES = '\u21AA'; // rightwards arrow with hook: continued + + function wrap_code_line(line ,columns){ + if(columns < 8 || line.length <= columns) return [line]; + const out = []; + + // The opening fragment surrenders one column to the break mark. + out.push(line.slice(0 ,columns - 1) + BREAKS); + let rest = line.slice(columns - 1); + + // Fragments that are themselves full carry both marks. + while(rest.length > columns - 1){ + out.push(RESUMES + rest.slice(0 ,columns - 2) + BREAKS); + rest = rest.slice(columns - 2); + } + + // The last fragment resumes and is set flush right. + const tail = RESUMES + rest; + out.push(' '.repeat(Math.max(0 ,columns - tail.length)) + tail); + return out; + } + + function columns_available(el){ + const probe = document.createElement('span'); + probe.style.font = window.getComputedStyle(el).font; + probe.style.visibility = 'hidden'; + probe.style.whiteSpace = 'pre'; + probe.textContent = '0'.repeat(100); + el.appendChild(probe); + const char_w = probe.getBoundingClientRect().width / 100; + el.removeChild(probe); + if(!(char_w > 0)) return 0; + + const cs = window.getComputedStyle(el); + const inner = el.clientWidth + - parseFloat(cs.paddingLeft || 0) + - parseFloat(cs.paddingRight || 0); + return Math.floor(inner / char_w); + } + RT.task_add('element' ,function(){ const U = window.RT.Utility; const config = window.RT.layout_config || {}; @@ -52,7 +115,11 @@ let offset_px = metrics.baseline_diff * (exact_px / 100); if(is_block){ - exact_px *= 0.95; + /* A fixed pitch face reads larger than a proportional one at the same + size, since its widest glyphs set the measure. The tradition is a + point or two smaller: eleven against twelve, sometimes ten. The ink + ratio corrects for the face; this corrects for the impression. */ + exact_px *= ((RT.config && RT.config.code && RT.config.code.size_ratio) || 0.85); let tagIndent = ''; const prevNode = el.previousSibling; @@ -95,6 +162,39 @@ } apply_style(el ,is_block ,exact_px ,offset_px ,text_color ,overlay ,config); + + /* Wrap after styling, since the columns available depend on the size just + set. An overflow is reported as well as repaired: a line long enough to + need carrying is usually a line the author would rather rewrite, and a + wrapped listing is a compromise even when it is a correct one. The + report names the block and the first line that overflowed, so the source + can be found without hunting. */ + if(is_block){ + const columns = columns_available(el); + if(columns > 0){ + const lines = el.textContent.split('\n'); + let first_over = -1; + let longest = 0; + const wrapped = []; + for(let k = 0; k < lines.length; k++){ + if(lines[k].length > columns){ + if(first_over === -1) first_over = k; + if(lines[k].length > longest) longest = lines[k].length; + } + const parts = wrap_code_line(lines[k] ,columns); + for(let p = 0; p < parts.length; p++) wrapped.push(parts[p]); + } + if(first_over !== -1){ + el.textContent = wrapped.join('\n'); + el.setAttribute('data-rt-wrapped' ,'true'); + window.RT.Debug.error('code' + ,'code block overflows the measure: ' + columns + ' columns available ,' + + 'longest line ' + longest + '. Wrapped with continuation marks. ' + + 'First overflowing line ' + (first_over + 1) + ': ' + + lines[first_over].trim().slice(0 ,60)); + } + } + } } }); diff --git a/developer/authored/Manuscript.copy/Element/theme_selector.js b/developer/authored/Manuscript.copy/Element/theme_selector.js index 71c2a0e..ba78029 100644 --- a/developer/authored/Manuscript.copy/Element/theme_selector.js +++ b/developer/authored/Manuscript.copy/Element/theme_selector.js @@ -38,7 +38,21 @@ container.style.color = 'white'; container.style.fontFamily = 'sans-serif'; - let html_content = `Theme Selection
`; + /* A widget, not part of the document. + + It overlaps the text once the window is narrow enough to fit the page, + and it has nothing left to say once a theme has been chosen. So it can + be dismissed. Nothing is persisted: a reload brings it back, which is + the behaviour wanted for a control that is occasionally needed and + usually not. */ + let html_content = ` +

+ Theme selection + × +
`; if (available_theme_keys.length === 0) { html_content += `No themes found in library.`; @@ -70,6 +84,13 @@ } }); + const dismiss = container.querySelector('.RT·theme-dismiss'); + if(dismiss){ + dismiss.addEventListener('click' ,function(){ container.remove(); }); + dismiss.addEventListener('mouseenter' ,function(){ dismiss.style.opacity = '1'; }); + dismiss.addEventListener('mouseleave' ,function(){ dismiss.style.opacity = '0.6'; }); + } + el.replaceWith(container); }); });