49: Braille Production - transcript

Download the MP3

Liam: The QUILL Cast, episode forty-nine. I'm Liam. Today we take braille out of the abstract and put it in the embossing room. This is the production episode: file, page geometry, repair loop, sidecar, validation, all of it. The full course arc runs fifty-four episodes total, and we are forty-nine of fifty-four. Five more after this one.

Jessica: I'm Jessica. Last episode was the bundled Quillins, episode forty-eight, where we walked the seventeen extensions that ship in every install, including brf-tools, the Quillin that registers .brf and .brl as braille file types and offers four tabs of preferences. The honest correction from that episode is still load-bearing: brf-tools declares min_quill_version 0.6.0, and the braille production surface we are about to exercise is much younger. If you are on an older build, some of the commands we will name today will be stubbed or absent, and the spoken error will be candid about which.

Liam: Frame for the next forty minutes. Braillists already know the work; this episode is about what QUILL adds. Page geometry is a real fact about your embosser, not a preference. Read Layout Metrics tells you the truth before any paper is loaded. The repair loop fixes the silent killers. The proofing sidecar tracks who has read which page. The validator audits a whole file for ten categories of problems. The Translation submenu converts print to braille on demand. And the brf-tools preferences page exposes every knob the toolchain respects.


Jessica: A do this now beat before we touch the code, because you will hear a lot of names. If QUILL is running, open a small braille document, any .brf, even a sample you keep around. If you don't have one, open a plain text file and rename the extension to .brf after saving a copy. The brf-tools file_type handler will fire and announce the file type, and the page map will build. Then run the command palette, open it with the QUILL key plus P, and type the words layout metrics. Find the Read Layout Metrics command. Bind it. Or just run it once. We'll come back to the announcement in a moment. Pause the audio, do it, and come back. The episode is more useful if you have heard the metrics announced at least once on a real file.

Liam: Good. Welcome back. Let's walk the actual code so the rest of the episode lines up with what shipped. The braille surface is split across four files in quill/ui. main_frame_braille.py is phase one: status announcements, navigation, page tools, the Translation submenu. main_frame_braille_phase2.py adds print-page detection, print-page navigation, and running heads. main_frame_braille_phase3.py adds the Proofing submenu and the Validation submenu. main_frame_braille_repair.py is the one today's episode lives inside: it is the small mixin that owns the Braille Repair submenu, Read Layout Metrics, Go to Longest Line, Go to Longest Page, and the two trailing-space strippers.

Jessica: The wx-free logic lives under quill/core. brf_document.py defines the plain-data shape: a frozen text snapshot plus cell width and line height. brf_page_map.py turns that snapshot into a list of BRFPage records, with three page-break strategies: form_feed for clean BRFs, calculated for foreign files that lost their FFs in transit, and hybrid, the default, that tries form feed first and falls back to calculated. braille_position.py is the resolver: it turns any character offset into a BraillePosition with page, line, cell, all 1-based. brf_repair.py is today's centerpiece: it computes the NLS-style layout metrics and provides longest-line, longest-page, and trailing-space helpers. brf_validator.py audits a file for ten categories of warnings. And brf_proofing.py and brf_sidecar.py hold the sidecar-backed proofing workflow we will explain in a moment.


Liam: Let's start with the four file types QUILL knows are braille. The io layer, in quill/io/open_read.py, defines a frozenset of four suffixes: dot brf, dot brl, dot pef, dot ueb. BRF is Braille Ready Format, the workhorse, plain NABCC ASCII with form feeds as page breaks. BRL is its earlier twin, the same character set, the same rules, the only difference is the file extension; producers ship whichever their toolchain defaults to. PEF is the Portable Embosser Format, a structured XML container that carries its own page geometry, designed so a document and its dimensions travel as one file. UEB in this list is the UEB-technical source form, before any back-translation. Honest correction: PEF and UEB round-trip through the read path, but the production commands target BRF and BRL by name. PEF is opened, displayed, and saved, and its text falls through the same repair and validator pipeline, but the Translation submenu and the brf-tools translation preferences are written for BRF output. We will say so again when we get to translation.

Jessica: That four-suffix contract is what makes the brf-tools file_type handler fire. Look at the manifest: it contributes a file_types entry that lists dot brf and dot brl as the two extensions, and the handler on_brf_opened announces the file type and, when the announce_page_count preference is on, the braille page count. PEF and UEB files are loaded as braille by the read path, but the brf-tools Quillin does not register handlers for them, so the announcement on open for a PEF is generic, not the brf-tools message. Worth knowing if you carry PEFs around.

Liam: Now the cell that defines your embosser. braille_cells_per_line and braille_lines_per_page are the two settings the entire repair and validation surface honors. The defaults, set in the BRFDocument and in brf_repair, are 40 cells per line and 25 lines per page, the standard interpoint values. Thirty-two cells is common for single-sided. The brf-tools preferences page exposes both, with sensible minimums and maximums: cells minimum twenty, maximum eighty, lines minimum ten, maximum sixty. If your embosser takes 28-cell paper, you set 28, not 40. The repair commands measure against whatever is in settings, so the announcement "Longest line 30 of 28 cells. Page width exceeded" only makes sense if 28 is the right number for your machine.

Jessica: And the page-break behavior, also in brf-tools. A real BRF produced by Duxbury or BrailleBlaster has form feeds every 25 or 28 lines, and the page map honors them. A BRF edited by hand, or one that lost its FFs in an email attachment, will trigger the calculated fallback. The hybrid mode is the safe default: form-feed first, calculated if no form feeds are present, and the mode is stored on the BRFPageMap so the status string can tell you which strategy produced the page count. The hybrid mode is also why the page count is never zero on a well-formed but FF-less file: an empty file gets one zero-length page so the status bar has something to announce. That is the floor under "the status bar never says page zero of zero."


Liam: Read Layout Metrics, the star of the repair submenu. The handler is in main_frame_braille_repair.py, in read_braille_layout_metrics. It reads the active BRF resolver, reads the editor text and cursor, and calls compute_layout_metrics from quill.core.brf_repair, passing the configured cells per line and lines per page. The result is a LayoutMetrics dataclass, and the spoken form is built by describe_layout, which is the function that produces the exact announcement you'll hear. Let's read it in plain English. Cell N of M. Longest line L of C cells, with the suffix "Page width exceeded" if L is over C. Braille page P of T, line X of Y. Longest page L of H lines, with the suffix "Page depth exceeded" if L is over H. Four numbers, two warnings. That is the announcement. It is the same shape as the NLS Braille Repair Tool, on purpose, because the workflow it supports is the same.

Jessica: The metrics are computed purely from the text and the page map. The cursor column is 1-based, the current line length is the cells in the line the caret sits on, and the longest line is computed by walking every line in the file. The longest page is computed by walking every page in the page map. There is no heuristic. If you change a character on page four, the longest-line value may change. If you delete a form feed, the longest-page value will change. The metrics are honest in the literal sense: they are the file, measured.

Liam: Honest correction, the first in this episode. The brief you read for episode forty-eight says nothing about repair, and the original ep34 braille production script predates the repair mixin. The repair submenu is the NLS-BRT parity work that landed after that, and it is documented in main_frame_braille_repair.py and brf_repair.py. The two files are the source of truth, not the older audio. The metrics workflow we are walking right now is the work of the brf_repair module, not the original braille mode.


Jessica: The repair loop, then. The four commands are Read Layout Metrics, Go to Longest Line, Go to Longest Page, Remove Trailing Spaces on This Line, and Remove Trailing Spaces in Whole File. The pattern is a closed cycle. Read metrics. Find the worst offender with Go to Longest Line. Fix it. Read metrics again. Find the new worst. Fix that. Repeat until the longest line is at or under your cell width, then the same for the longest page. Then strip trailing spaces whole-file as a final pass. Each "Read" announces exactly where you are. Each "Go" jumps there. Each "Strip" announces the count of characters removed. The loop is honest because the metrics are honest.

Liam: Go to Longest Line is interesting. The handler calls longest_line_offset from brf_repair, which iterates the file's lines and returns the character offset of the first character of the longest one. The handler then records the current location in the location ring, moves the caret to the offset, sets focus, records the new location, and announces "Longest line: N cells", with "Page width exceeded" appended if the line is over your limit. This is a real navigation, not a peek. You can press the QUILL key plus Left to go back. The location ring is a feature of the editor we covered back in episode eleven.

Jessica: Go to Longest Page does the same for pages, with a small but important twist. The handler calls longest_page_offset, which walks the page map and returns the offset of the first character of the page with the most lines. The announcement names the page number, the line count, and the depth-exceeded suffix if appropriate. So a thirty-line page on a twenty-five line setting says "Longest page: braille page 14 of 87, 30 lines. Page depth exceeded." That is the metric the user is being told. The page map is the same one the status bar uses, so the metric and the status agree.

Liam: Trailing spaces. The two strippers come last because they are the clean-up. Remove Trailing Spaces on This Line operates on the line containing the caret; if there is nothing to remove, it announces "No trailing spaces on this line." If there are, it announces the count, with the singular or plural noun chosen by the count. The implementation, strip_trailing_spaces_current_line in brf_repair, walks back from the end of the line and strips spaces and tabs only. It does not touch the line ending. The new cursor position is clamped to the new line end so the caret never lands past where you can read.

Jessica: Remove Trailing Spaces in Whole File is the file-wide version. The implementation, strip_trailing_spaces_all, uses a regex that matches runs of spaces and tabs immediately before a line ending or the end of file, and removes them. The line endings themselves are preserved exactly: LF stays LF, CR stays CR, CRLF stays CRLF, form feed stays form feed. The announce message is the same shape, just with "from the file" appended. This is the work that prevents "looks fine on screen, wraps on the embosser." Trailing spaces don't read on a braille display, but they count as cells on the page, and a 41-cell line on a 40-cell embosser is a wrap. Strip them, and the embosser is happy.


Liam: The proofing workflow, then, which lives in the Phase 3 mixin. The pattern is: open a saved braille file, navigate to a page, mark it proofed or mark it needs review, add a note if needed, and continue. The proofing state lives in a sidecar file, written next to the .brf with the same stem and a .brfproof extension, and the braille file itself is never modified. That is the contract: the sidecar is the proofing state, the BRF is the content, the two never mix. The sidecar survives reopens, and it survives the file being moved, because it travels as a sibling. The validator, which we will get to, also reads the sidecar for the proofed_pages field, so the validator knows which pages you have already checked.

Jessica: The commands are Mark Current Page Proofed, Mark Current Page Needs Review, Clear Proofing Mark, Add Proofing Note, Read Progress Summary, List Proofed Pages, List Pages Needing Review, and Export Proofing Report. The sidecar is loaded on first action, missing or malformed sidecars get a fresh BRFSidecar so the first mark starts clean. The save is atomic through the same write path the rest of the editor uses. There is no race, no partial state on disk. If you stop in the middle of a session, the sidecar reflects what you marked.

Liam: Read Progress Summary is the one you actually use, and it is the one the detailed-status command also draws on. The summary announces the last proofed page, the count of pages needing review, and the percentage. The validator-side progress overlay reads from the same sidecar. Export Proofing Report writes a plain-text report to a path you choose; the default name is the file stem plus -proofing.txt, so agenda.brf produces agenda-proofing.txt. The report is the kind of artifact you hand to a project manager, a teacher, or a client.


Jessica: The validator, the Validation submenu in the same Phase 3 file. Validate BRF Layout walks the file and emits a list of BRFWarning records. Ten categories. line_too_long: a line over the cell width. page_too_long: a page over the line height. page_too_short: a page with very few lines, which is a likely stuck page. missing_form_feeds: a long file with no form feeds at all, which is a likely page-break problem. mixed_line_endings: CRLF and lone LF in the same file, reported but not corrected. non_brf_ascii: a byte outside printable NABCC ASCII that is not a braille Unicode character. page_indicator: a page-separator line whose anchor is missing or malformed. page_numbering: a gap or duplicate in the detected page-number sequence. running_head: a running head that disagrees across pages. unicode_braille: the file is in Unicode braille, U+2800 to U+28FF, rather than NABCC ASCII.

Liam: The validator is read-only. The docstring says so. The findings are emitted as a list, sorted by offset, and shown in a SingleChoiceDialog that lists each one with its line, page, severity, and message. Choose one and the editor jumps to the offset and announces the message. The Next Warning and Previous Warning commands step through the list with delta plus one and minus one, with the same bounds checks the rest of the editor uses. Warnings Summary is the read-only one, it speaks the top three categories by count, with the singular or plural chosen by the total.

Jessica: The two severity levels are info and warning. The validator's output is not a panic button. A page_too_short on a one-page document is a false positive, and the validator knows that; the check is gated on more than one page. A unicode_braille on a PEF you imported for back-translation is a feature, not a bug, and the warning tells you so in the message: "this file should be NABCC ASCII." The validator's job is to tell you what is in the file. What you do with the information is the work.

Liam: Honest correction, the second in this episode. The brf_proofing module's mark_proofed and mark_needs_review functions drop the marked page from the other list as a side effect, so a page cannot be both proofed and needing review. The single source of truth is the sidecar. The validator's warning categories do not include a "proofing incomplete" category; the proofing overlay is a separate concern, drawn from the sidecar, and the detailed status is the place where proofing and layout are surfaced together.

Jessica: The Translation submenu, the last big surface in main_frame_braille.py. The menu is built dynamically when the QUILL Braille Pack is installed, and not shown in safe mode or when the pack is absent. The first section, hardcoded for quick access, is UEB, Unified English Braille. Two grades: Grade 1 uncontracted, Grade 2 contracted. Translate Selection to UEB takes only the selected text. Back-Translate UEB takes the selection if there is one, or the whole document, and produces a draft you can read against the braille to recover the source. The second section is Standard American English, the legacy EBAE workflows: Grade 1 and Grade 2. The third section, More Languages, is built from the brf_profiles.json file the pack ships. Languages with a single profile appear flat; languages with multiple profiles are grouped into a submenu. The pack provides more than the four hardcoded profiles.

Liam: The handlers all funnel through translate_and_open in main_frame_braille.py, which calls into the braille worker, a separate process running liblouis. The back-translate path calls worker.back_translate with a draft flag, the forward path calls worker.forward_translate, and both raise WorkerError on failure. The success message announces the page count using _brf_page_count, which builds a page map and counts. The result opens in a new tab. The brf-tools preference open_result_in_new_tab controls that; default is true, and the description is honest about why. Replacing the current document with a translation is not recommended.

Jessica: The pack guard lives in every handler. If the pack is absent, the handlers return an empty list from _braille_translation_items, the menu is not built, and the commands are not registered for binding. The command palette, however, registers the commands anyway, because the registry is the public surface. A user who calls Translate to UEB Grade 2 from the palette when the pack is absent will get a WorkerError, and the spoken error is candid. The first-time path: the menu offers "Download Braille Translation Pack" in place of the submenu when the pack is absent, a one-click way to the same outcome. The pack is unbundled for the same reason the heavy engines are unbundled: most users don't need it, and those who do know they do.


Liam: The brf-tools preferences page, in the manifest at quill/quillins_bundled/brf-tools/manifest.json. Four tabs. Translation: default profile, back-translation label, open-result-in-new-tab, announce-page-count. Page Handling: cells-per-line, lines-per-page, warn-on-long-line, normalize-line-endings-on-save. Status Bar: show-braille-page, show-line-in-page, show-cell-in-line, show-print-page, status-verbosity. Advanced: log-translation-commands, translation-timeout-seconds. The preferences are read by the host, not by the Quillin, and the values are written back to the same settings the repair and validation surface honors. The Quillin is the configuration UI; the host is the consumer.

Jessica: A note on cells per line and lines per page in the manifest. The minimum for cells is 20, the maximum is 80. The minimum for lines is 10, the maximum is 60. The default for both is the standard interpoint: 40 and 25. If your embosser takes 28-cell paper, you set 28 in this preference, and the repair surface, the validator, and the status bar all measure against 28. The settings are the contract.

Liam: The status-verbosity preference is interesting. The choices are brief, normal, and detailed. The braille_status module's spoken_status function dispatches on this value; brief is "Page 12. Line 14. Cell 31." normal is "Braille page 12 of 87. Line 14 of 25. Cell 31 of 40. Print page 7." detailed adds continuation, running head, last proofed page, review queue, and confidence. A user who wants the page count without the rest picks brief. A user who wants everything picks detailed. The default is normal, which is the right amount of information for most working sessions.

Jessica: The status-verbosity dispatch lives in the braille_status module, and the function is _verbosity_of, which reads the value off the profile object, lower-cases it, and returns normal for any unknown value. The misconfigured-profile case is intentional: a bad value never produces a blank status. The status always says something. Brief is a good escape hatch when the user is in a hurry, and detailed is the right choice when the user is in a meeting and wants to know which page is which.

Liam: A second do this now beat, because the production workflow benefits from muscle memory. Open the brf-tools preferences page now: Preferences, then Extensions, then Quillin Manager, then find BRF Tools in the list, then open its preferences. Set the cells per line to your embosser's spec. Set the lines per page to match. Set the status verbosity to detailed, just for the next few minutes, so you can hear the full announcement. Then close the preferences and re-run Read Layout Metrics on the file you opened earlier. Hear the longest line, the longest page, the cell width, the line height, all of it. Pause the audio, do it, and come back. The production pass in the next segment assumes you have heard a real metrics announcement at least once today.


Jessica: Welcome back. A production pass, end to end, for something real. A meeting agenda, the kind that ships as paper in a binder. Author it in QUILL, the part-two tools all apply. When you are ready for the braille side, run Save As and choose BRF as the format. Open the brf-tools preferences once and set cells per line and lines per page to your embosser's spec. Then read the metrics. Find the longest line. Fix it. Read the metrics. Find the longest page. Fix it. Strip trailing spaces whole-file. Validate the file. Mark the file proofed page by page. Export the proofing report. Emboss one proof. The machine checks catch everything measurable, so your human proofread spends itself on language, where humans are irreplaceable.

Liam: Compare two BRF revisions with the compare tools we covered in episode fourteen, hearing exactly which lines changed between drafts. The Spoken Echo keeps the metrics announcements. Verbosity profiles apply. And the same source document can leave as a DAISY talking book and a BRF, digital ears and physical fingertips, one master. That was the same observation in episode twenty-four, and it is still true.

Jessica: A note on the cell-two display workaround, the one in main_frame.py around line 11060. The default RichEdit 3.0 surface, which is the QUILL default, has a long-standing Word quirk: some braille displays show the first character of each line in cell two instead of cell one. That is issue #616, a known interaction between RICHEDIT50W and braille display drivers. The QUILL richedit surface has a Rich2 surface as a fallback, with the same controls, and the experimental emulate-sysedit surface in quill/ui/richedit_rtf_surface.py was a Phase 3 attempt to fix the dots 7-8 selection issue from #813; the on-device result was that the dots fix worked, but the cell start regressed to cell 5 and injected characters appeared, so the experimental is not on by default. The honest story is: cell two is the surface, not the braille code. The braille code is the BRF; the surface is the editor. Two different layers.


Liam: Honest correction, the third in this episode. The brf-tools manifest's capabilities list includes ui.status, which lets the Quillin read status-bar state. The schema for capabilities, which is in quill/core/schemas/extension.json, calls that out by name. The Quillin does not write the status cell; the host does. The Quillin observes. The capability model is the literal fence around the work: the Quillin can ask, the host grants, and the Quillin never gets more than it asked for. That is the same consent model every other Quillin lives under.

Jessica: Honest correction, the fourth. The original ep34 audio for this episode predates the repair submenu, the proofing workflow, the sidecar, and the validator. The script you are hearing is a re-issuance under the new numbering, and the content is the current state of the braille production surface. The audio file at docs/podcast/audio/ep34-braille-production.wav is the older material and should not be used as the reference. The reference is the code, and the code is the source of truth.

Liam: Homework, four steps. One: open or export any BRF, set your embosser's cells per line and lines per page in brf-tools preferences, and run Read Layout Metrics. If you have never heard your document measured, it is a small revelation: "Longest line 41 of 40 cells. Page width exceeded" is the line that turns an embosser from a device you trust into a device you verify. Two: run one full repair loop. Longest line, fix, metrics, longest line, fix, metrics, until clean. Then longest page, same loop. Then strip trailing spaces whole-file. That is the production cycle. Three: run Validate BRF Layout on the same file, read the top three categories in Warnings Summary, and address each one in the order the validator surfaces it. The validator's order is offset-then-kind, so a long line near the top of the file is found before a running head in the back. Four: take two revisions of the same BRF and run them through Compare; hearing exactly which lines changed between drafts is the braille proofreader's secret weapon. The Compare tool is the same one you use for sighted documents, and the braille-aware cell view makes the differences obvious.

Jessica: Next episode is fifty, quillins and the developer console, the second-to-last. We open the hood on the extension system: the Quillin Manager, the developer console for building your own, the safety model we have been waving at for the last two episodes, and the way the schema, the linter, and the runtime agree on what a Quillin is. That episode sets up fifty-one, which walks you through building your own. The full course arc runs fifty-four episodes total, and we are forty-nine of fifty-four. Five more after this one: fifty, fifty-one, fifty-two, fifty-three, and the finale.

Liam: The thread we keep returning to, screen reader first, consent specific, escape hatch guaranteed. The repair submenu is screen-reader first because every command speaks. The brf-tools preferences are consent specific because every setting declares a default and a description. Safe Mode is the escape hatch, the way the entire extension surface goes away with one restart. The braille code is not a feature buried in a menu; it is the editor's whole posture toward a physical medium. The validation is honest, the metrics are honest, the page map is honest. The proofing state lives in a sidecar that travels with the file. The translation respects the pack contract. That is the shape, and that is what you can audit.

Jessica: I'm Jessica.

Liam: I'm Liam. Measure twice, emboss once, and we'll see you in the developer console.

Back to all episodes