Tagged: paragraph format reflow
- AuthorPosts
- November 15, 2024 at 6:46 pm #30096gningParticipant
When you are editing text with a hard right margin, it helps to be able to reflow a paragraph to fit that width. The editor has the basics in it — steps where you can set a right margin and then convert soft newlines to hard ones — but not only is this a multistep process that would benefit from being encapsulated into a macro, it also produces paragraphs that don’t look nice. So I made a macro that not only encapsulates this reflow action, but also improves the aesthetics of the resulting paragraph.
Here’s an example of what I mean. Here is a paragraph of HTML from a web page which has a link in it, which due to its width produces a line that doesn’t reflow well:
And here’s that paragraph with my improved reflow, which rebalances long and short lines to make things more even:
It specifically avoids reflowing on the space within an
a href...
HTML tag. It could be expanded to handle tags more generally, but I ran into difficulty selection.Replace when I tried to use regular expressions. And for that matter with using the Regex object itself. I ended up using JS’s built-in Regex.It also preserves the indent if a paragraph is indented.
Here is the macro script, reflow selection with indent and smoothing.jsee:
// NOTE: wrap width is set by Current Configuration / General if (document.selection.IsEmpty) { alert("Select a paragraph or paragraphs.\r\n(You haven't put in automatic selection yet.)"); Quit(); } // Need a separate reference var which stays consistent through the duration, // or making changes to the config values WILL NOT STICK. cfg = document.Config; // Save current wrap and indent settings, and selection endpoints: var stateNoWrap = editor.QueryStatusByID(4208); // No Wrap var stateWindowWrap = editor.QueryStatusByID(4210); // Wrap by Window var statePageWrap = editor.QueryStatusByID(4318); // Wrap by Page var oldMargin = cfg.General.MarginNormal; var oldIC = cfg.Indent.IndentColumns; var oldTC = cfg.Indent.TabColumns; // Indent() actually uses this var anchorX = document.selection.GetAnchorPointX(eePosLogical); // starting end of selection var anchorY = document.selection.GetAnchorPointY(eePosLogical); var activeX = document.selection.GetActivePointX(eePosLogical); // cursor end of selection var activeY = document.selection.GetActivePointY(eePosLogical); // Determine current indent so margin can be adjusted, then restore selection: document.selection.StartOfLine(false, eeLineLogical | eeLineHomeText); var indentage = document.selection.GetActivePointX(eePosLogical) - 1; document.selection.SetAnchorPoint(eePosLogical, anchorX, anchorY); document.selection.SetActivePoint(eePosLogical, activeX, activeY, true); // Set Wrap by Characters mode so we can update margin and indent size: cfg.Indent.IndentColumns = cfg.Indent.TabColumns = 1; cfg.General.MarginNormal = oldMargin - indentage; cfg.Save(); // Before we use the built-in reformatter, we want to protect <a href...> tags from being broken up. document.selection.Replace("<a href", "<a°href", eeFindReplaceSelOnly | eeFindReplaceQuiet | eeReplaceAll); // regex mode not working // The reason we're here -- unindent, reflow, and reindent: document.selection.UnIndent(indentage); editor.ExecuteCommandByID(4209); document.selection.Format(eeFormatJoinLines); document.selection.Format(eeFormatSplitLines); // Re-get updated endpoints, which should now be in normalized order with anchor first: var anchorX = document.selection.GetAnchorPointX(eePosLogical); var anchorY = document.selection.GetAnchorPointY(eePosLogical); var activeX = document.selection.GetActivePointX(eePosLogical); var activeY = document.selection.GetActivePointY(eePosLogical); // Our secret sauce: after the built-in reflow is done, smooth it over by finding long lines // followed by short ones, and moving words down in order to even out the line lengths. var shorty = true; var motion = 0; var pass = 1; var rex = /\s+(\S+)\s*$/; // Em's internal RegEx class didn't work at all for (var follow = activeY; follow > anchorY; follow--) { // the line number being wrapped into document.selection.SetActivePoint(eePosLogical, 1, follow, false); document.selection.EndOfLine(false, eeLineLogical); var widthFol = document.selection.GetActivePointX(eePosLogical) - 1; document.selection.StartOfLine(false, eeLineLogical | eeLineHomeText); var dentFol = document.selection.GetActivePointX(eePosLogical) - 1; if (dentFol >= widthFol) { shorty = true; continue; // consider deleting trailing space here (none should occur?) } document.selection.LineUp(false); document.selection.EndOfLine(false, eeLineLogical); var widthLead = document.selection.GetActivePointX(eePosLogical) - 1; if (widthLead <= dentFol) { follow--; shorty = true; continue; } document.selection.StartOfLine(true, eeLineLogical | eeLineHomeText); // select lead line // extract final space-delimited word of leading line, and any whitespace next to it var mc = rex.exec(document.selection.Text); if (mc && mc[0] && widthLead - widthFol > mc[0].length && !shorty) { // does word fit better below? var word = mc[0]; motion += widthLead - widthFol; document.selection.EndOfLine(false, eeLineLogical); document.selection.DeleteLeft(word.length); document.selection.LineDown(false); document.selection.StartOfLine(false, eeLineLogical | eeLineHomeText); var mtrim = rex.exec(word); if (mtrim && mtrim[1]) word = mtrim[1]; document.selection.Text = word; document.selection.Text = " "; follow++; // retry the same line with another word } else shorty = false; // In bad cases, do the whole smoothing operation again. Usually each pass only finds about // a third as much work as was done last time. We put a hard limit on passes just in case. if (follow - 1 <= anchorY && motion >= 16 && pass < 4) { shorty = true; pass++; follow = activeY; } } // Restore selection and indentation, and <a href...> tags: document.selection.SetAnchorPoint(eePosLogical, anchorX, anchorY); document.selection.SetActivePoint(eePosLogical, activeX, activeY, true); if (indentage > 0) // if you pass 0 it still indents by 1 document.selection.Indent(indentage); document.selection.Replace("<a°href", "<a href", eeFindReplaceSelOnly | eeFindReplaceQuiet | eeReplaceAll); // To clean up, restore previous wrap and indent settings: cfg.General.MarginNormal = oldMargin; cfg.Indent.IndentColumns = oldIC; cfg.Indent.TabColumns = oldTC; cfg.Save(); if (stateNoWrap >= eeStatusLatched) editor.ExecuteCommandByID(4208); else if (stateWindowWrap >= eeStatusLatched) editor.ExecuteCommandByID(4210); else if (statePageWrap >= eeStatusLatched) editor.ExecuteCommandByID(4318); // Done.
- AuthorPosts
- You must be logged in to reply to this topic.