bibletext

package module
v1.2.15 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 23, 2026 License: Apache-2.0 Imports: 61 Imported by: 0

README

BibleText

CI

A clean, modern reader for the Bible that runs on macOS, Windows, Linux, iOS, and Android from a single Go codebase, built with Fyne. It presents the World English Bible (WEB), its Catholic edition (WEBC) with the deuterocanon, the Berean Standard Bible (BSB) — all public domain — and the licensed New King James Version (NKJV), in a calm, responsive reading layout with poetry set as poetry and the words of Christ in red. Chapters play as complete human-recorded narration with read-along highlighting, and any verse can be sent with a short note of your own riding inside an ordinary link — it opens beside the passage in the app (tapped on iPhone, iPad and Android, clicked on a Mac with the App Store edition, pasted into the search box on any desktop), or right in the recipient's browser. No accounts, no server, no ads.

Reading Study with AI Share as image
Reading view with the words of Christ in red Study any verse with AI Share a verse as an image
Distraction-free reading — words of Christ in red Explain, context & translation notes, with your own AI key Any verse as shareable art with a clean citation
More: cross-references & Gospel parallels

Cross-references and Gospel parallels

Tap a verse to surface its cross-references (Treasury of Scripture Knowledge) and, for a Gospel passage, the parallel accounts in the other Gospels.

Download

bibletext download page — all platforms in one place, or directly:

  • iPhone, iPad & Mac — App Store
  • Windows — Microsoft Store — signed, and updated for you
  • Android — sideload APK from Releases (built + signed locally by scripts/build-android.sh --release, uploaded per release; the Google Play listing is with Google on the closed testing track — see docs/PLAY_LISTING.md — and there is no production release yet)
  • Linux, from the Snap Store — snap install bibletext, or snapcraft.io/bibletext. Intel, AMD and ARM 64-bit; signed, updates itself, and registers the bibletext: link scheme, which the two downloads below do not do on their own.
  • macOS / Windows / Linux — grab the latest build from Releases. On a Mac the App Store edition, on Windows the Microsoft Store edition and on Linux the snap are the easier routes: signed and updated for you. These builds are unsigned, so on macOS right-click → Open the first time. Desktop artifacts are built by release.yml on every v* tag.
  • Linux, as a single file — BibleText-x86_64.AppImage or BibleText-aarch64.AppImage; chmod +x it and run it. The static runtime needs no libfuse2, and the file carries zsync update information, so a tool such as AppImageUpdate can refresh it in place. Unlike the tarball it does not register the bibletext: link scheme — paste a shared link into Search.

Build

You need Go 1.24 or newer, plus a C compiler (Fyne uses cgo): on macOS the Xcode Command Line Tools (xcode-select --install — Intel and Apple Silicon both work); on Linux also the GL/X11 and ALSA headers:

sudo apt-get install gcc libgl1-mesa-dev xorg-dev libxkbcommon-dev libasound2-dev   # Debian/Ubuntu

Then, from the repo root:

go run ./cmd/bibletext

That's the whole thing. A first run opens immediately on an embedded Gospels seed and downloads the complete Bible in the background (~30 seconds), caching it locally — so every launch after that is instant and works offline.

Install by module path — the route the Fyne apps directory offers. With git and the C toolchain above, plus the Fyne CLI (go install fyne.io/tools/cmd/fyne@v1.7.2), this clones the newest tag, builds it, and installs a packaged BibleText where your OS keeps applications:

fyne install github.com/cubancorona/bibletext/cmd/bibletext@latest

The plain Go route, go run github.com/cubancorona/bibletext/cmd/bibletext@latest, works the same way. Both resolve @latest to the newest tag, and cmd/bibletext exists only from the first tag cut after 19 September 2026 — the directory was cmd/desktop before that, so against an older tag either command stops at a path that is not there. …@main builds today, and tags before 1.2.6 additionally declare the module by a bare name that Go's module resolution rejects. A build from source by either route carries no bundled NKJV key — add your own free API.Bible key in Settings for that translation; the other translations need nothing.

iOS simulator (needs macOS with full Xcode, an iOS simulator runtime, and the Fyne CLI — the script checks and tells you what's missing): ./scripts/run-ios-sim.sh

Android (needs a JDK, the Android SDK + NDK, and the Fyne CLI — setup in docs/ANDROID.md): ./scripts/build-android.sh produces an installable debug APK.

Release builds, iOS device, Android, cross-compile, tests
# A standalone desktop binary (native for your OS/arch — Intel and Apple Silicon both fine)
go build -o bibletext ./cmd/bibletext

# macOS: build for the other Mac architecture (cgo needs the explicit opt-in)
CGO_ENABLED=1 GOARCH=arm64 go build -o bibletext-macos-arm64 ./cmd/bibletext
CGO_ENABLED=1 GOARCH=amd64 go build -o bibletext-macos-amd64 ./cmd/bibletext

# Linux/Windows builds: Fyne uses cgo, so a bare GOOS=… cross-build won't work —
# build natively on each OS, or use fyne-cross (https://github.com/fyne-io/fyne-cross).

# iOS simulator (needs the pinned Fyne CLI)
go install fyne.io/tools/cmd/fyne@v1.7.2
./scripts/run-ios-sim.sh

# Android — always via the wrapper script (a bare `fyne package -os android`
# drops the native reading overlay and the background-audio service):
./scripts/build-android.sh              # debug APK
./scripts/build-android.sh --release    # signed .aab + universal APK

# Tests
go test ./...

iOS device installs need Xcode signing; scripts/run-ios-device.sh wraps it (set BIBLETEXT_TEAM_ID to your own Apple Developer team id, and optionally BIBLETEXT_DEVICE_ID to pick a specific device). The iOS scripts also apply the Fyne patches to a local copy (see patches/README.md for the current set — setup-fyne-patch.sh applies them all); go.mod ships stock Fyne so plain go commands need no setup.

Android toolchain setup (JDK 21, SDK + NDK — all installable under $HOME, no root), signing, emulator use, and distribution are covered in docs/ANDROID.md.

Features

  • 📖 Responsive reading — scripture flows as a centred column that wraps to the window width with a comfortable line length, and superscript verse numbers.
  • 🔍 Smart search — keyword search across every verse with the matched terms highlighted, plus reference lookups like John 3:16, Ps 23, or 1 Cor 13 (common abbreviations are understood). An exact verse reference jumps straight to that verse in context.
  • 🧭 Quick navigation — filterable book list, previous/next chapter, and a chapter picker grid.
  • 🕮 Recent history — a slim, unobtrusive bar of recently read chapters you can jump back to, or clear.
  • 🌗 Light & dark mode — a warm "paper" light theme or an easy-on-the-eyes dark theme.
  • 📋 Copy — copy the current chapter to the clipboard.
  • ⌨️ Keyboard shortcuts (desktop) — Cmd/Ctrl+F focuses search, Esc clears.
  • 📱 Touch UI (iOS & Android) — one Read / Books / Search layout with full-size touch targets and native text selection (a real UITextView / TextView reading pane). Navigation sits along the bottom in portrait; a tablet or Android phone in landscape moves the same three destinations to a rail on the left so the short edge remains available for reading. iPhone keeps its bottom bar. On the Read tab a phone in landscape reads full-screen instead — no bar or rail, and on iPhone the page takes the iPad typography; rotate back for the navigation. The reading page on iPad keeps a centred, book-like measure with comfortable leading and indented paragraphs, modelled on the U.S. Reports. See docs/IPAD.md.
  • 🤖 AI study (bring your own key) — select any passage and have an AI Explain it, Analyze context, or Analyze translation, using your own Gemini / ChatGPT / Claude / Grok API key. There's also an AI Find that turns a plain-language request into matching passages on the Search tab. Optional and off-able (Settings → Assistant → None). See AI study for exactly what is sent.
  • 🔗 Cross-references & Gospel parallels — select a verse and choose Cross-references to see related passages (vote-ranked), each a tap away. For a Gospel verse, the same event in the other Gospels appears first, tagged Parallel (an embedded synopsis that works offline). Cross-reference data is the public-domain/CC-BY OpenBible.info set, fetched once and cached.
  • 🎧 Listen (all platforms) — play the current chapter from the reading header as a recorded narration (human, except for the WEB-Catholic's Greek books) or on-device read-aloud (text-to-speech) of the verses on screen. The Berean Standard Bible (Barry Hays) and the World English Bible (David Williams) both have complete public-domain narrations, streamed from the project's own audio mirror. The WEB-Catholic's Greek books — which no human public-domain narration covers — use a public-domain synthetic voice, labelled as such rather than as a narrator; everything else falls back to read-aloud — all fetched only when you press play. A person icon marks a recording and a waveform marks read-aloud; tap it to choose the source. Read-along: a floating Follow narration button keeps the page in sync, highlighting each verse as it is spoken. When a chapter finishes, playback continues to the next chapter automatically until you pause. On the phones (iOS and Android) audio keeps playing while the app is backgrounded or the screen is locked, with lock-screen / notification controls and ±15-second skip. On Windows and Linux the recorded narrations play too, with ±15s skip, continuous chapters, and the same read-along verse highlighting; on-device read-aloud (TTS) remains a native-platform feature (iOS / Android / macOS).
  • 📜 Poetry as poetry — the poetic books (Psalms, Proverbs, Job, the prophets' oracles, the embedded songs) display their authored verse lines — one poetic line per line, ragged-right, breaking at every verse boundary inside a poem, as in print — in all four translations, on every platform. Text shares, chapter copies, and the verse of the day keep the same lines.
  • 🟥 Red-letter mode — the words of Christ in red, on by default and switchable in Settings → Reading (every platform: the Windows/Linux styled pane colours per-run text like the rest, so the switch is shown there too).
  • ✦ Verse of the day — a subtle sparkle in the header opens one Christ-centred verse that rotates daily, with a jump to read it in context.
  • 📤 Share a verse — from the selection menu: Share with citation (text + reference) or Share as image (a clean, text-only card — no imagery — with a dynamic colour treatment and elegant serif typesetting; preview first, and Regenerate walks 13 colour schemes × 7 embedded book serifs — Gelasio, Cardo, Crimson Text, Spectral, Libre Baskerville, Prata, and DM Serif Display — 91 distinct pairings, identical on every platform). Quote and citation follow Bluebook style: spelled-out translation, en-dash ranges, and the Rule 5 quotation rules (the 50-word block-quote threshold, quotation nesting, bracketed capitals, and " . . . ." end omissions). Ragged drag edges are tidied — a selection cut mid-word trims to the whole word, stray verse-number markers never leak into the quote, and the citation always names exactly the verses the shared words come from. Text shares retain source poetry lines and reading paragraphs, but never line breaks caused only by screen wrapping. Share as link sends a bibletext.co.uk URL that opens the chapter in the static web reader with the shared verses highlighted — no app needed on the receiving end. Public-domain translations show the passage; an NKJV link shows the reference, attached note, public-domain parallels, and a route into the app without publishing licensed text. All open your device's native share sheet.
  • 📚 Multiple translations — read three public-domain translations: the World English Bible (WEB), the Berean Standard Bible (BSB), and the World English Bible (Catholic) with the 73-book deuterocanon — switchable from the header. The NKJV is available with an API.Bible key. See Bible versions.

Bible versions

The reader ships with three public-domain translations — the World English Bible (WEB), the Berean Standard Bible (BSB), and the World English Bible (Catholic) (WEB plus the 73-book deuterocanon) — all free to distribute and fetched in a single request each from the free, key-less bible.helloao.org. Use the translation switcher in the header (the version name beneath "BibleText") to change versions. One licensed translation (NKJV) is available through API.Bible:

Version Abbrev Rights holder Status
World English Bible WEB Public domain ✅ Real text
Berean Standard Bible BSB Public domain (CC0) ✅ Real text
World English Bible (Catholic) WEBC Public domain ✅ Real text
New King James Version NKJV Thomas Nelson (HarperCollins Christian) ✅ Available — via API.Bible

The NKJV is now available: it is served by API.Bible rather than redistributed, and is selectable when an API.Bible key is present. Current Store releases include the project's key; a reader can replace it with their own in Settings → Translations. The full retrieval, cache, switching, search and AI-study path is wired for licensed translations generally, so a newly licensed translation becomes a normal, selectable one the moment its license is configured (see Activating a licensed version) — no UI or code change needed.

For internal QA of a licensed translation before its license lands, set BIBLETEXT_ENABLE_TESTING=1. That unlocks any not-yet-licensed version with clearly-labeled placeholder text and a TESTING badge, so switching, navigation, search and AI study can be exercised end to end — without shipping copyrighted text. A default build registers no such version.

Getting a license

Two routes: go through an API provider that already carries the translation (simplest — it matches the licensedAPISource code path), or license directly from the rights holder and load the text they supply. A direct licence usually arrives as a data file or feed rather than an API bibleId, which plugs in as a file-based source instead of the licensedAPISource path below.

API.Bible (scripture.api.bible) — the provider the code scaffolds against, run by the American Bible Society; carries many popular translations. Confirm a translation is actually in its catalog before relying on it.

  1. Sign up at https://scripture.api.bible/ → get your API key from the dashboard once approved (sent in the api-key request header). This is BIBLE_API_KEY.
  2. A distributed app needs commercial access — copyrighted translations start around $10/month each; the free Starter plan's 3 licensed Bibles are non-commercial only. Arrange commercial terms with them.
  3. Get each translation's bibleId: GET /v1/bibles returns the Bibles your key can access, each with an id. That id is your BIBLETEXT_PROVIDER_ID_*.
API.Bible release credential

Current distributed builds include the project's API.Bible key. The supported mobile development packaging scripts do as well. The credential is never stored in tracked or generated repository source: build tooling reads only a dedicated external environment/Keychain value, transforms it in memory, and injects the obfuscated value into the final executable at link time. Those builds fail if the dedicated credential is unavailable.

At runtime the compiled key is an in-memory fallback. It is not copied into Preferences or Keychain, and a reader-supplied key takes precedence.

A client binary cannot make an embedded key secret. The obfuscation prevents an accidental plaintext string match, not extraction. Handling and provider follow-up are documented in docs/API_KEY_HANDLING.md.

The reader's own key (NKJV)

As an alternative to the Store release's project fallback, the reader can use their own API.Bible key without operator configuration. A reader can create an API.Bible account, add the New King James Version to their app, and paste the key into Settings → Translations. The reader-owned key is stored on-device (Keychain on iOS), takes precedence over the compiled fallback, and the text downloads under that reader's API.Bible licence. Licensed text is refreshed at least every 30 days under the provider terms and removed if the key is cleared.

Activating a licensed version (operator/env)

Once you hold a license and have provider credentials, no code change is needed — set these environment variables (the source is licensedAPISource in versions.go, fetching through the API.Bible client in apibible.go; licensed caches are revalidated every 30 days and purged when the licence configuration is removed):

export BIBLE_API_KEY="<your provider api key>"
export BIBLETEXT_LICENSE_<ID>=1                 # explicit "we are licensed" opt-in
export BIBLETEXT_PROVIDER_ID_<ID>="<provider's bible id>"

The double gate — a license opt-in and credentials — makes it impossible to ship copyrighted text by accident. Each version caches to its own file (bibletext-<id>.json) beside the WEB cache.

Those env vars drive the API-provider path (licensedAPISource) — the right shape for a licensed translation via API.Bible. A translation licensed DIRECTLY arrives as data, not an API, so it plugs in differently: add a small file-based bibleSource that parses the supplied text into BibleData (the bibleSource interface in versions.go is built for exactly this — webSource, licensedAPISource, and a future licensedFileSource all satisfy it, and the rest of the app is unchanged). Gate it the same way so the real text only loads once you've dropped the licensed file in place.

AI study (bring your own key)

Select a passage in the reader and the native selection menu gains a Study with AI submenu with three actions — Explain, Analyze context, and Analyze translation. The chosen action plus the selected text are sent to an AI provider of your choice, and the answer appears in a panel. (A free-form Ask a question… verb also exists in the code but is not currently surfaced in the menu.) The whole AI surface can be turned off in Settings → Assistant → None. A separate AI Find on the Search tab (the Search / Find toggle) takes a plain-language request and returns matching passages — using only the references the model names, with the verse text coming from the app's own Bible data. AI answers carry a Report button (to flag any output) and the AI-settings sheet shows an in-app note explaining what leaves the device.

You supply your own AI-provider key per provider. AI-provider keys are stored only on this device — in the Apple Keychain on iOS (encrypted at rest, and carried across an encrypted backup or a move to a new device) and in the local preferences store on macOS, Windows, Linux, and Android — no AI-provider key is embedded in the app. (The separate API.Bible fallback used to fetch the licensed NKJV is described above.) Open the header gear → AI study sheet to pick a provider and paste a key:

Provider Model Get a key
Gemini (Google) gemini-pro-latest (faster: gemini-2.5-flash) https://aistudio.google.com/apikey
ChatGPT (OpenAI) gpt-5 (faster: gpt-4o-mini) https://platform.openai.com/api-keys
Claude (Anthropic) claude-opus-5 (faster: claude-haiku-4-5) https://platform.claude.com/settings/keys
Grok (SpaceXAI) grok-4.5 (faster: grok-4.3) https://console.x.ai

The Model dropdown in the same sheet is populated live from your provider's own model list (fetched with your key), so new models appear the day they ship — pick one to pin it, or leave Recommended to use the default above, which self-heals automatically if the provider retires it.

A <PROVIDER>_API_KEY environment variable (GEMINI_API_KEY, OPENAI_API_KEY, ANTHROPIC_API_KEY, XAI_API_KEY) overrides the stored key when set.

What gets sent

Each action builds one prompt (buildAIPrompt in ai.go) and sends it as a single user message. Output is limited only by a 32768-token runaway backstop — the prompt, not a spending cap, is what keeps answers short (a lower cap silently starved the reasoning models, which count their hidden thinking against it). Gemini and Grok requests are sent at temperature 0.4, ChatGPT too except its reasoning families (gpt-5, o*), which — like Claude — use the provider's default. Identical requests are cached in memory, so re-opening the same analysis does not re-send. Only the text you selected — plus the book and chapter it came from — and the fixed instructions below ever leave the device:

You are a knowledgeable, even-handed Bible study assistant. Write in clear,
plain language for a general reader and keep it concise — a few short paragraphs
at most. Where scholars disagree or a point is uncertain, say so briefly rather
than overstating. Do not use markdown headings or bullet lists.

{task}

Passage ({Book} {Chapter}):
"{selected text}"

{task} is the only part that differs per action:

  • Explain — "Explain what the passage below means: its main idea, any imagery or terms a general reader might not know, and how its parts connect."
  • Analyze context — "Explain the context of the passage below: who wrote it and to whom, what is happening in the surrounding narrative, and how it fits the historical, literary, and theological themes of {Book}."
  • Analyze translation — "Discuss translation considerations for the passage below: notable Hebrew or Greek words behind the English, how major English translations render it differently, and nuances that are hard to carry into English. The quoted text is from the {Version}." (the name of the active translation — e.g. the World English Bible or the Berean Standard Bible)

The reference sent is the book and chapter only (e.g. Passage (John 1)), not the specific verse number. The separate Test key button in settings sends just Reply with the single word: OK to validate a key.

Repository layout

bibletext/
├── go.mod                  # module github.com/cubancorona/bibletext
├── *.go                    # shared package: bibletext
│   ├── bible.go cache.go fetch_bible_data.go annotation.go   (pure data layer)
│   ├── state.go theme.go font.go                              (cross-platform UI scaffolding)
│   ├── sidebar.go reading.go search.go history.go ui.go       (shared widgets)
│   ├── ui_desktop.go    # //go:build !ios && !android  — shared layout + keyboard shortcuts
│   ├── ui_mobile.go     # //go:build ios  || android   — shared touch layout + native seams
│   ├── ui_compact.go tab_rail.go                              (Read / Books / Search; bar or rail)
│   ├── ui_regular.go layout.go                                (retained former-layout/rotation machinery)
│   ├── reading_macos.go reading_ios.go reading_android.go     (native reading overlays)
│   ├── audio_macos.go audio_ios.go audio_android.go           (native audio engines)
│   └── app.go              # Run() / NewLoadingState() / StartBackgroundLoad() entry helpers
├── android/                # Java half of the Android bridge (compiled to
│   │                       # classes2.dex by scripts/build-android.sh)
│   ├── BtBridge.java       # selectable reading overlay + selection menu + share
│   ├── BtAudio.java        # MediaPlayer + TextToSpeech engine
│   └── BtAudioService.java # foreground service — background/lock-screen playback
├── scripts/                # build wrappers: build-android.sh, run-ios-*.sh, release-ios.sh
├── docs/ANDROID.md         # Android toolchain, build, signing, distribution
├── docs/IPAD.md            # unified iPad navigation, typography, testing, shipping
└── cmd/                    # six programs; the four app and site ones import the shared package
    ├── bibletext/          # `go build ./cmd/bibletext` · `fyne install …/cmd/bibletext@latest`
    │   ├── main.go
    │   ├── FyneApp.toml    # name, app ID, version/build (read by `fyne package` / `fyne install`)
    │   └── Icon.png        # app icon
    ├── mobile/             # iOS: scripts/run-ios-*.sh · Android: scripts/build-android.sh
    │   ├── main.go
    │   ├── FyneApp.toml         # bundle ID, version/build (read by `fyne package`)
    │   ├── AndroidManifest.xml  # custom manifest — media service + session permissions
    │   └── Icon*.png            # app icon + Android adaptive-icon layers
    ├── websitegen/         # the static web reader at bibletext.co.uk (scripts/publish-site.sh)
    ├── sitepages/          # the site's hand-written root pages (scripts/publish-site.sh)
    ├── linuxmeta/          # renders linux/, flatpak/ and snap/ from linux/listing.toml
    └── msstore/            # renders the Microsoft Store manifest, listing and tiles

The same bibletext package is consumed by the four application and site cmd/ programs — linuxmeta and msstore are standalone packaging generators that import none of it; build tags on ui_desktop.go / ui_mobile.go make the linker pick the platform-appropriate CreateMainUI implementation. Pure data files (bible.go, cache.go, fetch_bible_data.go, annotation.go) have no UI deps and compile everywhere.

License

The application's source code is licensed under the Apache License 2.0.

Bundled data and assets keep their own licenses (see NOTICE):

  • Scripture: World English Bible and Berean Standard Bible — public domain (via bible.helloao.org).
  • Audio narration: BSB by Barry Hays (CC0) and WEB by David Williams (public domain), plus a synthetic public-domain narration of the WEB-Catholic's Greek books from eBible.org, streamed from the project's audio mirror.
  • Cross-references: OpenBible.info Treasury of Scripture Knowledge — CC BY.
  • UI font: Atkinson Hyperlegible (Braille Institute) — SIL Open Font License 1.1.
  • Share-card serifs: Gelasio, Cardo, Crimson Text, Spectral, Libre Baskerville, Prata, DM Serif Display — all SIL OFL 1.1 (assets/fonts/share/OFL-LICENSES.txt).

"Your word is a lamp to my feet and a light to my path." — Psalm 119:105

Documentation

Index

Constants

View Source
const (
	// NumberingSame — every verse keeps its number; a reference may be carried
	// across as-is.
	NumberingSame = ""
	// NumberingMoved — the passage is in `to`, under a different number.
	NumberingMoved = "moved"
	// NumberingAbsent — `from` numbers a verse here that `to` simply does not
	// contain (the NKJV's Acts 8:37). Not a renumbering, and telling a reader it
	// is one would be false.
	NumberingAbsent = "absent"
	// NumberingIncommensurable — the two translations' versions of this BOOK do
	// not correspond verse by verse at all (WEBC's Greek Esther).
	NumberingIncommensurable = "incommensurable"
)

Numbering-difference kinds returned by ChapterNumberingDifference. They are plain strings rather than the unexported verseMapResult because the one caller outside this package (cmd/websitegen) turns them straight into a sentence for a reader, and a leaked enum would be a type it could not name.

View Source
const NoteMaxRunes = 280

NoteMaxRunes caps a note. It bounds the URL (a 280-rune note lands at roughly 290 characters of link, which messengers carry without wrapping or truncating) and it bounds the abuse surface of text we will render.

ENCODER-SIDE ONLY: the decoder must not enforce a rune cap and refuse a note over it (docs/NOTE_WIRE_FORMAT.md, the limits table) — normalizeNote truncates instead.

Variables

This section is empty.

Functions

func AlphabeticalBooks

func AlphabeticalBooks(books []string) []string

AlphabeticalBooks orders books the way the app's "Go to" picker does — a leading numeral read as an ordinal, so 1/2/3 John group under John.

func AppID

func AppID() string

AppID returns the mobile bundle/application identifier.

func BookFromSlug

func BookFromSlug(slug string) (string, bool)

BookFromSlug resolves a URL slug back to its canonical book name.

func BookSlug

func BookSlug(name string) (string, bool)

BookSlug returns a book's permanent URL slug.

func ChapterNumberingAgrees

func ChapterNumberingAgrees(from, to, book string, chapter, spanEnd int) bool

ChapterNumberingAgrees reports whether EVERY verse of one chapter keeps its own number when a reference is carried from translation `from` into `to`.

It exists for the web reader's parallel-passage links. The server never sees the verse — it rides in the fragment (share_link.go) — so the page has to decide at BUILD time whether "the same verse, in another translation" is a link it may offer at all. When this returns false the page offers the CHAPTER and says the numbering differs there, which is the honest answer; pointing confidently at a number that means a different passage is the worst failure this file exists to prevent.

spanEnd is the last verse number the chapter reaches in the REFERENCE translation (the WEB) — the caller has that, because the reference is one of the translations it loaded, and it does NOT have `from`'s own verse list (the point of the NKJV pages is that the site holds no NKJV data at all).

TWO THINGS THE REFERENCE'S SPAN ALONE WOULD MISS, both found by measurement rather than by reading:

  • verses `from` HAS and the reference LACKS — the NKJV's Acts 8:37, Acts 15:34, Acts 24:7 and Luke 17:36. All four happen to be numbered inside the WEB chapter's span today, but relying on that is luck.
  • verses `from` numbers INTO this chapter that the reference numbers elsewhere — the NKJV's Romans 16:25-27, which the WEB carries as 14:24-26. The WEB's Romans 16 stops at 24, so a 1..spanEnd scan never asks about 25 and calls the chapter safe. It is not: those three verses are exactly the ones a shared link would land in the wrong place.

It is deliberately CONSERVATIVE in the other direction: spanEnd may name verses `from` does not have (the WEB's Romans 14:24-26 against the NKJV's 23-verse chapter), so a chapter can be reported as disagreeing when no reachable reference would actually move. The cost of that is a chapter-level link and one extra sentence; the cost of the opposite is the reader being shown different words from the ones they were sent.

func ChapterNumberingDifference

func ChapterNumberingDifference(from, to, book string, chapter, spanEnd int) string

ChapterNumberingDifference reports HOW one chapter's verse numbers differ between two translations — NumberingSame when they do not.

When more than one kind applies the most severe wins, in the order incommensurable, absent, moved: they describe the same chapter at different scales, and a reader told only about the smallest would act on the wrong one.

func CreateMainUI

func CreateMainUI(app fyne.App, state *AppState, window fyne.Window) fyne.CanvasObject

CreateMainUI (desktop) assembles the whole window. The sidebar and the split are built once and stay put; navigation swaps only the reading pane's content, so the search and filter fields never lose focus. Toggling the theme rebuilds the tree with the new palette.

func DecodeNote

func DecodeNote(payload string) (DecodedNote, NoteOutcome)

DecodeNote is the inverse: the decoded record plus what to do about it. On any outcome other than NoteOutcomeOK the DecodedNote is zero — it never returns a partially decoded note, because the caller's next move is to render it.

A note arriving here is UNTRUSTED — it came from a URL that anyone can write. This function guarantees only that Text is well-formed, printable UTF-8 within the length cap. It is NOT sanitised for a markup context: every caller must insert it as text (textContent, an escaped HTML write, an attributed string), never as markup. See docs/SHARED_NOTES.md → Security.

func EmCSS added in v1.2.9

func EmCSS(v float64) string

EmCSS is emCSS for the website generator.

func EncodeNote

func EncodeNote(note string) string

EncodeNote is the text-only convenience over the record encoder: a payload carrying just 't'. Callers that know the passage should use EncodeNoteWire. It returns "" for a note that is empty once trimmed — callers then emit a plain link rather than one carrying nothing.

func EncodeNoteWire

func EncodeNoteWire(w NoteWire) string

EncodeNoteWire turns a note into the payload that rides in a shared link's fragment: byte 0 'r' (or 'd' when DEFLATE comes out smaller), then records in canonical form — ascending by tag, at most one of each, minimal uvarints.

The note is TRUNCATED rather than rejected if it is too long: a share is a gesture, and failing it outright at the last moment is worse than sending a slightly shortened note. Callers should hold the writer to NoteMaxRunes in the UI so this never fires.

func FirstLetter

func FirstLetter(book string) string

FirstLetter is the letter a book is filed under in that picker's alphabet grid ("1 John" → "J"). Exported with AlphabeticalBooks so the web picker groups and orders identically to the app rather than re-deriving the rule in JavaScript, where "1 John" would file under "1".

func GroupVersesIntoParagraphs

func GroupVersesIntoParagraphs(verses []Verse) [][]Verse

GroupVersesIntoParagraphs applies the app's paragraph rule (break after a sentence-ending verse once a paragraph is long enough). Exported so the web page breaks paragraphs in exactly the same places the reading pane does.

func HandleShareLink(state *AppState, rawURL string) bool

HandleShareLink is the single entry point for a tapped link, called from the platform glue on the UI goroutine. It returns false when the URL is not one of ours, so the caller can let the system open it in a browser instead.

func HighlightTintClass

func HighlightTintClass() string

HighlightTintClass is the CSS class a highlighted verse carries, from the app's own tint table (verseTint.htmlClass, tint.go).

The web reader is the ONE surface that cannot consume chapterTint: its pages are static and the tint is chosen at read time, in reader.js, from the URL fragment. What it can share is the VOCABULARY — and it must, because a shared link opened in the browser and the same link opened in the app are meant to light the same verses the same way. Exported so cmd/websitegen's tests can assert its hand-written CSS and JS still spell the class the way the app's emitter does; adding a second tint means adding a name here and a rule there, and the test is what says so out loud instead of the site quietly rendering the old single wash.

func IncommensurableBook

func IncommensurableBook(vid, book string) string

IncommensurableBook explains why a book cannot be mapped between the reference and vid, or "" when it can. Callers show this rather than inventing a verse.

func InstallDebugCapture

func InstallDebugCapture()

The shipping half of debug_capture_macos.go: nothing. Kept as a real symbol so the entry point can call it unconditionally and no build tag leaks into cmd/bibletext.

func InstallReadingStateFlush

func InstallReadingStateFlush(myApp fyne.App, window fyne.Window, state *AppState)

InstallReadingStateFlush captures the precise within-chapter scroll position when the app stops or backgrounds (and, on desktop, when the window is closed while the native text view is still alive). Navigation already saves the location + history continuously via persistReadingPosition; this is the only hook that catches a pure scroll with no navigation. Exported so both entry points (desktop Run and cmd/mobile) can install it.

func IsWordsOfChrist

func IsWordsOfChrist(book string, chapter, verse int) bool

IsWordsOfChrist reports whether a verse falls in a red-letter range.

DEPRECATED FOR RENDERING. This is the WEB's verse-level judgement and it is version-blind, so a page rendered through it reddens the WHOLE verse and reddens it identically in every translation. Use RedLetterRuns instead, which asks the edition's own table. Kept because it is still the honest answer to the question it actually asks ("does Christ speak in this verse at all"), and removing an exported symbol is not free.

func MapVerse

func MapVerse(from, to, book string, chapter, verse int) (int, int, verseMapResult)

MapVerse carries a verse reference from one translation's numbering into another's. It returns the chapter and verse to use in `to`, and what kind of correspondence that is — callers must look at the result, because a returned 0,0 means "there is no such verse there", not "verse zero".

Composed through the reference, so a BSB→NKJV mapping is BSB→WEB→NKJV. Both halves can fail independently: Romans 16:25 in the BSB is the WEB's 14:24 (moved), which is the NKJV's 16:25 (moved back) — the same passage, two moves, and the round trip lands where it started.

func ObserveSystemThemeChanges

func ObserveSystemThemeChanges(myApp fyne.App, state *AppState)

ObserveSystemThemeChanges subscribes to Fyne's settings-change channel so a system light/dark switch rebuilds the window. Fyne re-runs Color() automatically when the variant changes, but anything generated outside the theme callback (like the HTML the iOS UITextView consumes, or the palette colors baked into canvas objects at build time) is stale until we rebuild.

The rebuild goes through rebuildWindow, NOT a bare SetContent: SetContent replaces only the content tree and never touches Canvas().Overlays(), so an OPEN popup (the Settings sheet, a picker) survived a variant flip with its captured colors while Fyne re-lit its stock widgets — the resulting dark-panel/dark-text sheet after an overnight dark→light switch with the app suspended. rebuildWindow drains the overlay stack (popups close; reopening shows fresh colors) and re-pins the native reading overlay.

applyTheme calls app.Settings().SetTheme() the first time (and on a real theme change), which ALSO fires this listener — so we guard against a rebuild loop by only acting when the actual light/dark variant has changed since last time.

func PoeticJoin

func PoeticJoin(prevText, curText string) bool

PoeticJoin reports whether the boundary between two adjacent verses is a poetry line boundary — the rule that keeps the web page, the reading pane and a shared quote breaking in identical places.

func ProductName

func ProductName() string

ProductName returns the product's display name.

func ReadingHeadLeadEm added in v1.2.9

func ReadingHeadLeadEm() float64

func ReadingHeadTailEm added in v1.2.9

func ReadingHeadTailEm() float64

func ReadingLinePitchEm added in v1.2.7

func ReadingLinePitchEm() float64

ReadingLinePitchEm is the same number for the generated site, whose CSS line-height multiplies the font size directly — no numeral in the way.

func ReadingOpticalScale added in v1.2.7

func ReadingOpticalScale() float64

ReadingOpticalScale is the correction as the site generator needs it. The generated stylesheet sets Scripture in the same face as the app and has to open it up by the same amount, or a chapter read on the web is visibly smaller than the same chapter read in the app.

func ReadingParaGapEm added in v1.2.9

func ReadingParaGapEm() float64

The same numbers for the website generator, which lives in another package.

func ReadingReporterIndentEm added in v1.2.9

func ReadingReporterIndentEm() float64

func ReadingTitleGapEm added in v1.2.9

func ReadingTitleGapEm() float64

func Run

func Run()

Run is the desktop entry: loads the data, opens a sized window, and starts the event loop. Mobile entries (Fyne iOS) use the same data path but configure the window differently — see cmd/mobile/main.go.

func ShareLinkURL

func ShareLinkURL(versionID, book string, chapter, lo, hi int) string

ShareLinkURL builds the permanent web URL for a passage. lo/hi are verse numbers; pass lo <= 0 for a chapter-level link, or hi <= lo for a single verse. It returns "" only if the book is unknown — callers fall back to sharing plain text rather than a broken link.

A selection spanning several chapters passes its FIRST chapter and that chapter's verse span: the link then under-highlights rather than pointing at the wrong chapter. (The app cannot select across chapters today; fixing the rule now means tomorrow's app needs no new pages.)

func ShareLinkURLWithNote

func ShareLinkURLWithNote(versionID, book string, chapter, lo, hi int, note string) string

ShareLinkURLWithNote is ShareLinkURL plus an optional note from the sender. An empty note (or one that is empty once normalized) yields exactly the link ShareLinkURL would have produced — byte for byte — so adding this feature changed nothing about the links that do not use it.

func ShareLinkURLWithNoteNonce

func ShareLinkURLWithNoteNonce(versionID, book string, chapter, lo, hi int, note string, nonce []byte) string

ShareLinkURLWithNoteNonce is ShareLinkURLWithNote plus the note's per-share identity, which the sending device keeps so it can recognise its own note coming home (share_note.go, noteTagNonce). A nil nonce emits nothing and produces exactly the link the plain builder would — so every existing caller, and every link already in the world, is unaffected.

func SiteBase

func SiteBase() string

SiteBase returns the origin the published site and every shared link live on ("https://bibletext.co.uk"). Exported for the site generators, which build canonical URLs from it.

func SiteHost

func SiteHost() string

SiteHost returns SiteBase's bare host, the form deep-link matching uses.

func StartBackgroundLoad

func StartBackgroundLoad(myApp fyne.App, window fyne.Window, state *AppState)

StartBackgroundLoad kicks off the Bible load on a background goroutine and swaps the result into the live state on the UI thread when it's ready. The caller shows the window FIRST (with state.loadPhase == loadPending, so CreateMainUI renders just a spinner and never attaches the native reading overlay); this keeps the main thread free, so the iOS launch watchdog can't SIGKILL the app on a slow first-run fetch. On success we copy the loaded fields into the same *AppState the UI already closed over (never swap the pointer — the showReading/surfaceReading closures captured it) and rebuild; on failure we show an in-app retry view.

Exported so both entry points (desktop Run, cmd/mobile) use the same path.

func SupportEmail

func SupportEmail() string

SupportEmail returns the public mailbox used by product support surfaces. Its only tracked value lives in config/product.json.

func SupportMailtoRecipient

func SupportMailtoRecipient() string

SupportMailtoRecipient returns the configured mailbox escaped for the recipient component of a mailto URI.

func VerseExistsIn

func VerseExistsIn(vid, book string, chapter, verse int) bool

VerseExistsIn reports whether a verse named in the reference's numbering is present in a translation at all. Cheaper to read than MapVerse when the caller only needs to know whether to offer something.

func VerseIsPoetic

func VerseIsPoetic(text string) bool

VerseIsPoetic reports whether a verse carries authored poem-line breaks.

func VersionCacheEpoch added in v1.2.9

func VersionCacheEpoch(id string) int

cachePathForVersion is the on-disk cache for a version. An unversioned default (web) uses the legacy path (honoring BIBLETEXT_CACHE_PATH); other unversioned translations live beside it as bibletext-<id>.json. A version with a non-zero cacheEpoch gets bibletext-<id>-v<epoch>.json, including the default translation, so a stale cache produced by an older decoder is bypassed. VersionCacheEpoch is the decoder epoch a version's cache is keyed by — the number the app bumps whenever its decode of that edition changes. The site generator keys its own raw-feed cache by it, so a decoder change on this side refetches the feed rather than decoding a copy taken under an older understanding of it. 0 for an unknown id or an unversioned cache.

func WebNoteArrivalLeadPx

func WebNoteArrivalLeadPx() int

WebNoteArrivalLeadPx is the shared arrival lead (noteMetrics().Lead): how far below the top of the viewport an arrival places its target.

func WebNoteByline

func WebNoteByline() string

WebNoteByline is the shared byline for a received note, exactly as the app panes attribute one (senderByline). Sender names do not ship, so this is a generate-time constant for the static reader; the day names ship, TestWebReaderNoteChromeComesFromTheSharedFunctions holds the seam and the template learns names rather than silently keeping the constant.

func WebNoteGapAbovePx added in v1.2.9

func WebNoteGapAbovePx() int

The note band's air for the website generator: the spec's reservation above the card, the tail the shape hangs below it, the reservation below that, and the collapsed pill's height (noteMetrics; notes_bubble.go). CSS px stand for the natives' points.

func WebNoteGapBelowPx added in v1.2.9

func WebNoteGapBelowPx() int

func WebNotePillHPx added in v1.2.9

func WebNotePillHPx() int

func WebNotePillLabel

func WebNotePillLabel() string

WebNotePillLabel is the shared collapsed-pill label for the web reader's structural case — one placed note, nothing unplaced — from the same function every pane's pill reads (stickerPillWho).

func WebNotePillMinWPx added in v1.2.9

func WebNotePillMinWPx() int

func WebNotePillPadXPx added in v1.2.9

func WebNotePillPadXPx() int

func WebNoteTailDepthPx added in v1.2.9

func WebNoteTailDepthPx() int

func WebNoteTailWidthPx added in v1.2.9

func WebNoteTailWidthPx() int

func WebScriptureFontBold added in v1.2.7

func WebScriptureFontBold() []byte

WebScriptureFontBold is the subsetted reading face, bold. Required, and not obviously so: the only bold inside the reading column is the verse number, and with a webfont a weight of 600 resolves to the 700 face — so shipping regular alone would leave every verse number synthesised.

func WebScriptureFontLicense added in v1.2.7

func WebScriptureFontLicense() []byte

WebScriptureFontLicense is the reading face's licence. The OFL requires it to travel with the font, which is why the site publishes it beside the file.

func WebScriptureFontRegular added in v1.2.7

func WebScriptureFontRegular() []byte

WebScriptureFontRegular is the subsetted reading face (WOFF2). Built from the SAME file the app embeds, so the site and the app can never drift to different releases of it. Narrower than the app's subset: the site publishes no edition that marks a divine name, so it needs no small capitals, and its note chrome is set in the UI face, so its scripture face never draws Greek or Hebrew.

func WebUIFontBold

func WebUIFontBold() []byte

WebUIFontBold is the subsetted Atkinson Hyperlegible bold face (WOFF2).

func WebUIFontLicense

func WebUIFontLicense() []byte

WebUIFontLicense is the SIL Open Font License text that must be published alongside the faces above.

func WebUIFontRegular

func WebUIFontRegular() []byte

WebUIFontRegular is the subsetted Atkinson Hyperlegible regular face (WOFF2).

Types

type Annotation

type Annotation struct {
	Ref     VerseRef
	Note    string
	Color   string // optional highlight colour key; "" means a plain note
	Created time.Time
	Updated time.Time
}

Annotation is a user note and/or highlight attached to a verse. This is the data foundation for upcoming annotation/research features; the UI and persistence are intentionally not wired yet.

type AnnotationStore

type AnnotationStore struct {
	// contains filtered or unexported fields
}

AnnotationStore holds annotations keyed by verse reference. It is in-memory for now; persistence can follow the versioned, atomic JSON pattern in cache.go.

func NewAnnotationStore

func NewAnnotationStore() *AnnotationStore

func (*AnnotationStore) Add

func (s *AnnotationStore) Add(a Annotation)

Add stores an annotation, stamping timestamps when absent.

func (*AnnotationStore) Count

func (s *AnnotationStore) Count() int

Count returns the total number of annotations stored.

func (*AnnotationStore) ForVerse

func (s *AnnotationStore) ForVerse(r VerseRef) []Annotation

ForVerse returns the annotations attached to a verse, in insertion order.

func (*AnnotationStore) HasAny

func (s *AnnotationStore) HasAny(r VerseRef) bool

HasAny reports whether a verse carries any annotation (for gutter markers etc.).

func (*AnnotationStore) Refs

func (s *AnnotationStore) Refs(bookOrder []string) []VerseRef

Refs returns every annotated verse reference, sorted canonically by book order.

type AppState

type AppState struct {
	Bible *BibleData

	// Translation the reader is showing. CurrentVersion is a BibleVersion ID
	// (see versions.go); currentMode says whether Bible is real scripture or a
	// testing placeholder; loadedVersions caches already-loaded translations so
	// switching back is instant (the default/base version is always present).
	CurrentVersion string

	CurrentBook    string
	CurrentChapter int

	BookFilterQuery string

	SearchQuery              string
	ActiveSearchQuery        string
	SearchResults            []Verse
	SearchTruncated          bool
	IsSearching              bool
	CanReturnToSearchResults bool

	// The note attached to that highlight, when the reader arrived on a shared
	// link carrying one. Minimized means the reader collapsed it: the note is
	// kept but neither it nor its highlight is shown until they bring it back.
	ActiveNote    string
	NoteMinimized bool
	// NoteVerseLo is the verse the note is attached to. Kept separately from the
	// highlight because minimizing CLEARS the highlight — without this the note
	// would lose its anchor and its marker would jump to the top of the chapter.
	NoteVerseLo int
	// NoteID is the live note's identity in the scrapbook store
	// (StoredNote.ID) — the ONLY handle Hide, Show and Delete address. It is
	// handed to the mirror by the derive and carried whole; no verb ever
	// rebuilds a key from the version, book or chapter the reader happens to
	// be standing on. Rebuilding the address from the reader's position is
	// what deleted the wrong note (X1), made Hide and Show address different
	// objects (X5), and left a cross-chapter note unreachable by any verb
	// (X13). Zero means "no live note", or a note the store could not keep.
	NoteID uint64

	// NoteNotice is the sentence shown in the note's place when a link's
	// payload could NOT be rendered — a newer note format, or damage
	// (noteOutcomeMessage). Session-only and never stored: it is the app
	// reporting on a payload, not a message from a person, so it is attributed
	// to nobody, carries no action and no link, and the next navigation clears
	// it (addRecentChapter). See docs/NOTE_WIRE_FORMAT.md rule 5.
	NoteNotice string

	RecentChapters []ChapterVisit

	// IsFullScreen is the "distraction-free reading" toggle, honoured on every
	// platform: the shared compact layout (buildCompactUI, ui_compact.go)
	// returns the reading view alone — no app header, no bottom bar or rail —
	// and the desktop's opt-out sidebar layout has the same branch
	// (ui_desktop.go). The way out differs per reading view: on the desktop the
	// chapter toolbar stays and its focus button restores the layout
	// (chapterHeader, reading.go); on iOS/Android the reading view drops the
	// chapter toolbar and draws its own small exit row with the chapter
	// reference (buildReadingViewMobile). The mobile toolbar's full-screen
	// button (chapter_header_mobile.go) and the desktop toolbar's focus button
	// are the ways in. The PRESENTED mode is readingFullScreen
	// (phone_landscape.go): this choice, or the phone-landscape presentation
	// (on by default on phones, Read tab only), which never writes this flag.
	IsFullScreen bool

	// CurrentTab is the selected mobile bottom-bar tab: 0 Read, 1 Books,
	// 2 Search. The mobile UI rebuilds the window on tab change (reliable
	// repaint) rather than swapping a content host in place.
	CurrentTab int

	// NotesMode is the Search tab's third mode: browsing the notes people have
	// shared. Mutually exclusive with aiSearchMode — searchModeOf is the one
	// place that resolves the pair, so no caller has to remember the rule.
	NotesMode bool

	// NotesQuery filters the notes browser. It is deliberately NOT the keyword
	// search query: switching Search → Notes with a scripture term still in the
	// box would greet the reader with "no notes match" for a search they never
	// made of their notes.
	NotesQuery string

	// Annotations is the foundation for note/highlight + research features. It is
	// populated/persisted by future work; the reading view already renders verses
	// as selectable, individually-referenceable blocks.
	Annotations *AnnotationStore
	// contains filtered or unexported fields
}

AppState holds everything the UI renders from, plus hooks the widgets install so state-mutating helpers can request a redraw without knowing about widgets.

func NewLoadingState

func NewLoadingState() *AppState

NewLoadingState returns a minimal AppState in the loadPending phase, valid for CreateMainUI to render the loading spinner before any Bible data exists. The entry points hand this to the window, then call StartBackgroundLoad.

type BibleData

type BibleData struct {
	// Verses is a nested map (map of maps) organizing all verses
	// First key: book name ("John", "Genesis", etc.)
	// Second key: chapter number (1, 2, 3, etc.)
	// Value: slice of all Verse objects in that chapter
	Verses map[string]map[int][]Verse

	// Books is the edition's book names in canonical order (66, or 73 for the Catholic canon)
	// Used to display the book list in the sidebar
	Books []string

	// OrphanFootnotes carries the translators' notes anchored in verses the
	// translation OMITS (see OrphanFootnote), keyed book → chapter. Nothing
	// but the chapter-bottom footnote section reads it — it is invisible to
	// search, speech, share, copy and links by construction, because those
	// all walk Verses. omitempty: caches written before this field existed
	// load with a nil map, and every accessor is nil-safe (the superseded-
	// epoch fallback serves such caches to offline upgraders).
	OrphanFootnotes map[string]map[int][]OrphanFootnote `json:"orphan_footnotes,omitempty"`

	// Superscriptions carries the Psalm titles (see Superscription), keyed
	// book → chapter. omitempty + nil-safe accessors, for the same
	// pre-field-cache reasons as OrphanFootnotes.
	Superscriptions map[string]map[int]Superscription `json:"superscriptions,omitempty"`

	// Headings carries the publisher's own section headings, keyed by book and
	// chapter (see Heading). Every edition sets them and the app used to drop
	// every one: 3,091 in the Berean alone, which is its translators' map of
	// what each chapter is about. They are captured here whether or not any
	// surface draws them, because a thing a publisher sent is kept.
	//
	// Like a superscription, a heading is NOT Scripture. It never enters
	// Verse.Text, so it cannot reach search, speech, sharing, copying or a
	// link by accident; a surface that wants to draw one asks for it.
	Headings map[string]map[int][]Heading `json:"headings,omitempty"`
	// contains filtered or unexported fields
}

BibleData holds all Bible verses organized by book and chapter This is the data model/storage for the entire Bible Structure:

  • Books: the edition's book names in canonical order (66, or 73 for the Catholic canon)
  • Verses: map[book][chapter] = list of verses Example: Verses["John"][1] = [Verse1, Verse2, ...]

func DecodeCanonical66

func DecodeCanonical66(body []byte) (*BibleData, error)

DecodeCanonical66 decodes a 66-book helloao complete.json (WEB, BSB).

func DecodeHelloAOCatholic

func DecodeHelloAOCatholic(body []byte) (*BibleData, error)

DecodeHelloAOCatholic decodes the 73-book WEB Catholic complete.json.

func FetchBibleFromAPI

func FetchBibleFromAPI() (*BibleData, error)

FetchBibleFromAPI fetches the complete World English Bible from a public API This uses the free bible-api service which has the complete WEB translation Source: https://bible-api.com/

func NewBibleData

func NewBibleData() *BibleData

NewBibleData creates and initializes a new BibleData structure This sets up the empty data structure ready to be populated with verses

func (*BibleData) GetChapter

func (bd *BibleData) GetChapter(book string, chapter int) []Verse

GetChapter returns all verses in a specific chapter Returns an empty slice if the chapter has no verses This is the main function for displaying a chapter Example: GetChapter("John", 1) returns all verses in John chapter 1

func (*BibleData) GetChapterNumbersForBook

func (bd *BibleData) GetChapterNumbersForBook(book string) []int

GetChapterNumbersForBook returns sorted available chapter numbers for a book. When the chapterNums cache is present (the normal loaded-Bible case) it returns the shared cached slice — callers only read/range it, so there's no defensive copy. Falls back to computing for directly-constructed BibleData (tests).

func (*BibleData) GetChaptersForBook

func (bd *BibleData) GetChaptersForBook(book string) int

GetChaptersForBook returns the number of chapters in a book Used by the navigation buttons to determine if we can go to the next chapter Example: GetChaptersForBook("John") might return 21 (John has 21 chapters)

func (*BibleData) GetVerse

func (bd *BibleData) GetVerse(book string, chapter int, verse int) *Verse

GetVerse returns a specific verse from the Bible Returns nil if the verse doesn't exist Parameters:

  • book: name of the book (e.g., "John")
  • chapter: chapter number
  • verse: verse number

Example: GetVerse("John", 3, 16) returns "For God so loved the world..."

func (*BibleData) OrphanNotesFor

func (bd *BibleData) OrphanNotesFor(book string, chapter int) []OrphanFootnote

OrphanNotesFor returns the chapter's omitted-verse footnotes, or nil. Nil-safety is load-bearing at every level: the superseded-epoch fallback serves pre-field caches (nil map) to offline upgraders, and placeholder BibleData carries no orphans at all.

func (*BibleData) PopulateWithSampleVerses

func (bd *BibleData) PopulateWithSampleVerses()

PopulateWithSampleVerses loads a small set of World English Bible (public domain) verses. This is demo/fixture data used by tests and offline examples; the running app always loads the complete WEB text from cache or the API.

func (*BibleData) PrepareSearchIndex

func (bd *BibleData) PrepareSearchIndex()

PrepareSearchIndex precomputes normalized verse text + reference strings used by search, and the per-book sorted chapter-number cache. It is the heavy per-load pass (~31k verses), so it runs on the background load goroutine before the UI ever sees the BibleData; afterwards the data is read-only.

func (*BibleData) Search

func (bd *BibleData) Search(query string) []Verse

Search searches for verses containing the given query text Returns a slice of all verses that match (case-insensitive) Example: Search("faith") returns all verses containing the word "faith"

func (*BibleData) SearchLimited

func (bd *BibleData) SearchLimited(query string, limit int) ([]Verse, bool)

SearchLimited searches for verses containing the query and caps returned results. It returns the matches and whether additional matches were omitted due to limit.

func (*BibleData) SearchSmartLimited

func (bd *BibleData) SearchSmartLimited(query string, limit int) ([]Verse, bool)

SearchSmartLimited supports verse reference queries and ranked term matching.

func (*BibleData) SuperscriptionFor

func (bd *BibleData) SuperscriptionFor(book string, chapter int) Superscription

SuperscriptionFor returns the chapter's Hebrew title, or the zero value — nil-safe at every level, like OrphanNotesFor.

type BibleVersion

type BibleVersion struct {
	ID        string // stable id; also the per-version cache filename ("web","lsb","nkjv")
	Name      string // full name, e.g. "World English Bible"
	Abbrev    string // short label, e.g. "WEB"
	Publisher string // one-line rights/copyright note, shown in the picker

	// LicenseNotice is the attribution the rights holder requires displayed
	// with the text — shown in the picker once the version is actually
	// licensed and serving real text (versionRow). Empty for public-domain
	// versions, whose Publisher line already says everything.
	LicenseNotice string

	// PublicDomain marks freely-distributable text (no license required).
	PublicDomain bool

	// PublisherCrossRefs shows the edition's OWN cross-reference apparatus in
	// the cross-references panel (publisher_xrefs.go), ahead of the Treasury
	// of Scripture Knowledge. False everywhere in a store build: displaying a
	// licensed publisher's apparatus is the subject of a licensing enquiry that
	// has been sent and not answered, so the one line that sets it — for the
	// NKJV — lives behind the nkjvxrefs build tag (versions_nkjvxrefs.go),
	// where no shipped binary compiles it.
	PublisherCrossRefs bool
	// contains filtered or unexported fields
}

BibleVersion describes one selectable translation.

type ChapterBlock added in v1.2.7

type ChapterBlock struct {
	// HeadingText is the heading, or "" when this block is a paragraph.
	HeadingText string
	// Verses is the paragraph's verses, nil when this block is a heading.
	Verses []Verse
}

ChapterBlock is one piece of a chapter as it is set on the page: either a publisher's section heading, or a paragraph of verses. The exported shape of what every reading surface now walks.

func ChapterBlocks added in v1.2.7

func ChapterBlocks(bd *BibleData, book string, chapter int, verses []Verse) []ChapterBlock

ChapterBlocks returns a chapter in the order it is set — the publisher's headings among its own paragraphs. Exported so the page places them exactly where the reading pane does.

type ChapterVisit

type ChapterVisit struct {
	Book    string
	Chapter int
	Verse   int     `json:"v,omitempty"`
	Delta   float64 `json:"d,omitempty"`
	Frac    float64 `json:"f,omitempty"`
}

ChapterVisit is one entry in the reading history. The scroll anchor (top verse + within-verse delta, with a whole-chapter Frac fallback) records where the reader was when they left this chapter, so tapping it in the history bar returns them there instead of to the top. A zero anchor means top-of-chapter. The anchor fields are omitempty so plain (top-of-chapter) entries and pre-anchor saved blobs stay compact and backward-compatible.

type DecodedNote

type DecodedNote struct {
	Text    string // normalized; non-empty exactly when the outcome is OK
	Version string // sender's translation id ('v'), "" when absent
	Book    string // canonical book name resolved from 'b', "" when absent
	Chapter int    // 'c', 0 when absent
	Runs    []NoteVerseRun
	Nonce   []byte // 'n', nil when absent — see noteTagNonce

	// Skipped holds every record this build could not USE, verbatim
	// (tag+len+value): unknown lowercase tags, and known tags whose value did
	// not parse. Preserved, not merely skipped (docs/NOTE_WIRE_FORMAT.md rule
	// 3), so a future forward/re-share can re-emit them instead of silently
	// destroying the sender's data on its way through us.
	Skipped [][]byte
	// Opaque is the 0xFF stop byte and everything after it, verbatim, for the
	// same reason.
	Opaque []byte
}

DecodedNote is everything a payload carried that this build can read. Zero-valued fields were absent; Runs distinguishes absent (nil) from present-with-zero-runs (empty, a real assertion — see docs/NOTES_SPEC.md#wire-contract on the 'a' record).

type Footnote

type Footnote struct {
	// Anchor is a rune offset into Verse.Text (0..len) at a word boundary —
	// where the source placed the marker. For a note the source anchored
	// between poem lines, Anchor sits at the end of the earlier line, before
	// the "\n".
	Anchor int `json:"anchor"`
	// Text is the note body — the translators' words. Origin references the
	// source prefixes to the body ("3:2 " in the NKJV apparatus) are
	// stripped at decode time: the anchor already says where it belongs.
	Text string `json:"text"`
	// Kind distinguishes apparatus families: "" is a translator footnote
	// (alternate rendering, manuscript variant, language note); "crossref"
	// is a cross-reference entry (the only kind the NKJV feed carries).
	Kind string `json:"kind,omitempty"`
	// Caller is the source's marker glyph ("+" throughout helloao, "-" in
	// the NKJV feed). Stored for fidelity; no surface renders it yet.
	Caller string `json:"caller,omitempty"`
	// Refs are the citations inside a cross-reference note that the source
	// tagged with a machine-readable target, in text order. Only the NKJV feed
	// tags them (its ref elements); the helloao editions' notes carry none and
	// leave this nil, as does a cache written before the field existed.
	// Offsets into Text, never characters added to it, so a note's words are
	// the publisher's own whether or not its citations were kept.
	Refs []NoteRef `json:"refs,omitempty"`
}

Footnote is one note from the TRANSLATORS (never the reader — reader notes are the separate shared-notes feature) anchored into a verse's text.

type FootnoteEntry added in v1.2.8

type FootnoteEntry struct {
	// Key is "Title" for a superscription's note and the verse number
	// otherwise — the same string every in-app renderer prints.
	Key  string
	Text string
}

FootnoteEntry is one row of a chapter's footnote section: the key it is filed under and the translators' words.

func ChapterFootnoteEntries added in v1.2.8

func ChapterFootnoteEntries(bd *BibleData, book string, chapter int, verses []Verse) []FootnoteEntry

ChapterFootnoteEntries is the chapter's apparatus in the order the app shows it: title notes first, then verse notes in verse order, with an omitted verse's note sorted into its natural place between its neighbours.

Exported as a WHOLE ROW rather than as its parts so the website cannot re-derive the ordering, the key or the cross-reference exclusion and drift away from the app. In particular the exclusion is not cosmetic: the NKJV's entire apparatus is cross-references, and whether they display at all is an open licensing question (docs/SCRIPTURE_WORKLIST.md S20).

type Heading added in v1.2.7

type Heading struct {
	// Text is the heading as the publisher set it.
	Text string `json:"text"`

	// Style is the publisher's classification: "heading" for the helloao
	// feeds, which name only the one kind, and the USX paragraph style for
	// API.Bible — "s" and "s1".."s4" for section heads, "ms" for a major
	// section, "r"/"mr"/"sr" for a reference line, "qa" for an acrostic
	// letter, and so on.
	Style string `json:"style,omitempty"`

	// Footnotes are the notes the publisher put INSIDE this heading. They were
	// discarded with the block that carried them, which left them with less
	// trace than any other note in the apparatus; they belong to the heading,
	// not to whichever verse happened to be current when it was read.
	Footnotes []Footnote `json:"footnotes,omitempty"`

	// BeforeVerse is the verse this heading stands above. A heading at the
	// head of a chapter names its first verse; one between verses names the
	// verse that follows it. Zero means the heading closed a chapter with no
	// verse after it, which the sources do not do but the decoders do not
	// assume.
	BeforeVerse int `json:"before_verse,omitempty"`
}

type Mark

type Mark struct {
	Origin hlOrigin
	At     VerseSpan
}

Mark is the highlight. The zero Mark is "nothing is highlighted", and it is the only way to say that.

type NoteKind

type NoteKind string

NoteKind says what sort of record this is. Extensible: a build that meets a kind it does not know keeps the record and simply does not draw it.

type NoteOutcome

type NoteOutcome uint8

NoteOutcome is what DecodeNote concluded about a payload. There is no silent arm: ok renders the note, and BOTH failure arms are TOLD to the reader (docs/NOTE_WIRE_FORMAT.md rule 5) — in the note's place, attributed to nobody, with no call to action and no link. The passage always opens.

const (
	// NoteOutcomeNone: the link carried no note payload at all. DecodeNote
	// never returns it — it exists so ShareTarget's zero value is honest.
	NoteOutcomeNone NoteOutcome = iota
	// NoteOutcomeOK: a note, decoded, plus every field that was present.
	NoteOutcomeOK
	// NoteOutcomeNewer: byte 0 in 'A'-'Z', or an unknown UPPERCASE record tag —
	// a note written in a newer note format than this build understands.
	NoteOutcomeNewer
	// NoteOutcomeDamaged: any framing failure. The one arm that rejects.
	NoteOutcomeDamaged
)

type NoteRef added in v1.2.8

type NoteRef struct {
	ID    string `json:"id"`
	Start int    `json:"start"`
	End   int    `json:"end"`
}

NoteRef is one tagged citation inside a note: the source's own target id ("JHN.7.50"; a range is "MAT.3.1-MAT.3.12") and the rune span within Footnote.Text that the citation's words occupy ("John 7:50"). A citation the source left untagged — the NKJV's parenthesised "(Acts 10:38)" — has no NoteRef; its words are still in Text.

type NoteVerseRun

type NoteVerseRun struct{ Lo, Hi int }

NoteVerseRun is one contiguous verse run of a note's anchor, inclusive.

type NoteWire

type NoteWire struct {
	Text    string
	Version string // the SENDER's translation id — not the (lossy) link path
	Book    string // canonical book name
	Chapter int
	VerseLo int    // 0 = no verse run
	VerseHi int    // 0 or < VerseLo = single verse
	Nonce   []byte // per-note identity (noteTagNonce); nil emits nothing
}

NoteWire is what the encoder is given. Only Text is required; everything else is emitted when present and meaningful.

type OrphanFootnote

type OrphanFootnote struct {
	Verse  int    `json:"verse"`
	Text   string `json:"text"`
	Kind   string `json:"kind,omitempty"`
	Caller string `json:"caller,omitempty"`
}

OrphanFootnote is a translators' footnote whose VERSE the translation omits — the critical-text omissions (Luke 17:36, Acts 8:37, 15:34, 24:7, Romans 16:25, plus deuterocanon versification gaps). The verse number exists in the versification but decodes to no text, so the note cannot ride on a Verse; it lives in BibleData.OrphanFootnotes and surfaces ONLY in the chapter-bottom footnote section, keyed by its verse number — the one place a reader can learn WHY that number is absent from the page.

type ShareTarget

type ShareTarget struct {
	// VersionID is one of the ids a link path may name (linkPathVersionIDs).
	// That is NO LONGER the same as the ids the web reader publishes, and it is
	// no longer a promise this reader can open it: /nkjv/ is a real link path and
	// a reader without the NKJV cannot be switched to it. Code downstream must
	// therefore handle "the link names a translation we do not have" rather than
	// assume it away — see applyShareTarget.
	VersionID string
	Book      string // canonical book name, e.g. "1 Corinthians"
	Chapter   int    // >= 1
	VerseLo   int    // 0 when the link names no verse (a chapter link)
	VerseHi   int    // 0 or == VerseLo for a single verse

	// Note is the sender's message, already decoded and normalized, or "" when
	// the link carries none or carries one we could not read.
	//
	// IT IS UNTRUSTED TEXT: anyone can write a link. Render it as TEXT, never as
	// markup, and never styled as if BibleText said it — see
	// docs/SHARED_NOTES.md → Security.
	Note string

	// NoteOutcome says what became of the link's note payload:
	// NoteOutcomeNone when the fragment carried no "n" key at all, otherwise
	// DecodeNote's verdict. The two failure outcomes are TOLD to the reader in
	// the note's place (share_link_open.go) — never silently dropped, never a
	// call to action (docs/NOTE_WIRE_FORMAT.md rule 5). The passage opens in
	// every case.
	NoteOutcome NoteOutcome

	// NoteNonce is the payload's per-note identity ('n'). It answers exactly one
	// question on arrival: is this the note I sent? (share_note.go,
	// noteTagNonce.)
	//
	// A fixed ARRAY, not a slice, so ShareTarget stays comparable — seven tests
	// compare whole targets with ==, and that is a good property to keep for a
	// value type describing a parsed link.
	//
	// The zero value means "no nonce": every link made before this existed, and
	// every link whose note this build could not read. An actual all-zero nonce
	// from crypto/rand has probability 2^-48, and its only consequence would be
	// that one note does not collapse — the safe direction.
	NoteNonce [noteNonceLen]byte

	// The note's OWN anchor, from the payload's v/b/c/a records — set only on
	// NoteOutcomeOK, zero when the record was absent. These are AUTHORITATIVE
	// for the NOTE where present: the path is lossy (webc forced for the
	// deuterocanon, unknown ids falling back to web), so the wire says what the
	// sender was actually reading. They do not navigate — the fragment verse
	// span above remains what places the page.
	NoteVersion string
	NoteBook    string
	NoteChapter int
	NoteLo      int // first run of the 'a' record; 0 when absent
	NoteHi      int

	// NoteRuns is the 'a' record's FULL run set — "43-43,45-46" — where
	// NoteLo/NoteHi above carry only the first run. A resolution is a set, not
	// a span (WEB Mark 9:43-46 lands in the BSB with a hole in it), and the
	// store files the whole set (rememberIncomingNote). A string in the
	// noteRunsSpelling grammar, not a slice, so ShareTarget stays comparable;
	// "" when the record was absent or empty.
	NoteRuns string

	// What the payload carried that this build could NOT use, preserved so the
	// store can keep it and a future forward/re-share can re-emit it
	// (docs/NOTE_WIRE_FORMAT.md rule 3). NoteSkipped is DecodedNote.Skipped
	// concatenated — each skipped record is self-framing (tag+len+value), so
	// the stream splits again — and NoteOpaque is the stop byte and everything
	// after it. Strings, not byte slices, so ShareTarget stays comparable.
	NoteSkipped string
	NoteOpaque  string
}

ShareTarget is a passage named by a shared link.

func ParseShareLink(raw string) (ShareTarget, bool)

ParseShareLink parses a BibleText web-reader URL into the passage it names. ok is false for anything that is not one of our reader links — including the landing, privacy and support pages, which must always stay in the browser.

Accepts (all of these appear in the wild):

type StoredNote

type StoredNote struct {
	// ID is this note's identity in the reader's scrapbook, and the ONLY thing
	// a verb ever addresses. Minted from a persisted monotonic counter
	// (prefNotesNextID) — NOT max(existing)+1, because deletion must never free
	// an ID for reuse.
	ID uint64

	// Kind: "received" | "mine". See NoteKind.
	Kind NoteKind

	// The anchor. VersionID is the translation the note was written against —
	// where it is FILED, not necessarily what is on screen.
	VersionID string
	Book      string
	Chapter   int
	VerseLo   int
	VerseHi   int

	// AnchorRuns is the FULL anchor as a run set
	// (docs/NOTES_SPEC.md#anchor-and-placement-contract)
	// — what the wire's 'a' record carried, when it carried one. A resolution
	// is a set, not a span: WEB Mark 9:43-46 lands in the BSB as [43,43] and
	// [45,45], which VerseLo/VerseHi cannot say. Empty for a note whose link
	// carried no 'a' record; VerseLo/VerseHi always hold the FIRST run, so
	// everything already reading them keeps working. Additive ("ar",
	// omitempty): a store written before this field serialises byte-identically
	// after it.
	AnchorRuns []anchorRun

	// Text is the message. UNTRUSTED: rendered as text on every surface, never
	// as markup.
	Text string

	// Minimized has ONE meaning: the reader closed this note
	// (docs/NOTES_SPEC.md#chapter-plan-and-presentation-state). Written only
	// by a reader's press.
	Minimized bool

	// Received is this device's clock at arrival, unix seconds.
	Received int64

	// SenderName / SenderID are RESERVED: carried, stored, never shown — as
	// SharedNote carried them before this store existed. When a name is shown
	// it will be untrusted text with its own display rules.
	SenderName string
	SenderID   string

	// WireSkipped / WireOpaque preserve what the link's payload carried that
	// the decoding build could not use (share_note.go DecodedNote.Skipped /
	// .Opaque): WireSkipped is the unknown records concatenated verbatim
	// (each is self-framing tag+len+value, so the stream splits again), and
	// WireOpaque is the stop byte and everything after it. Kept so a future
	// forward/re-share can re-emit them instead of silently destroying the
	// sender's data on its way through us (docs/NOTE_WIRE_FORMAT.md rule 3).
	// Nonce is this note's per-share identity, minted when YOU shared it and
	// carried in the link (noteTagNonce). It exists for one question: is this
	// arriving note the one I sent? Content cannot answer that — a friend may
	// write the same words on the same verse — and the wire carries no sender
	// identity by design.
	Nonce []byte

	WireSkipped []byte
	WireOpaque  []byte

	// Extra holds every JSON field of this record that this build does not
	// know, verbatim. A newer build's fields pass through an older build's
	// read-modify-write untouched. Never inspected, only re-emitted.
	Extra map[string]json.RawMessage
}

StoredNote is one record of the scrapbook store.

The anchor is VersionID/Book/Chapter plus a run set: AnchorRuns where the wire carried one, else the VerseLo/VerseHi span. Resolving it into the translation on screen is notes_anchor.go's job (S6), never an inline probe.

func (StoredNote) MarshalJSON

func (n StoredNote) MarshalJSON() ([]byte, error)

MarshalJSON emits the known fields in fixed order, then every Extra key in sorted order — deterministic bytes, so an unchanged store writes unchanged.

func (*StoredNote) UnmarshalJSON

func (n *StoredNote) UnmarshalJSON(data []byte) error

UnmarshalJSON reads the known fields AND keeps every field it does not know in Extra, verbatim. This is spec rule 1 of the long-term foundation: an older build's rewrite must not destroy a newer build's fields.

type Superscription

type Superscription struct {
	// Text is the title line, assembled by the same marked-text path verse
	// text uses, so its spacing rules are identical.
	Text string `json:"text"`
	// Footnotes are the title's notes; Anchor is a rune offset into Text
	// (stored for a future in-title marker, unrendered today).
	Footnotes []Footnote `json:"footnotes,omitempty"`
}

type TextRun

type TextRun struct {
	Text string
	Red  bool
	// Italic marks the words the translators supplied. The generated site sets
	// them in italic exactly as the app does, because the edition discloses
	// them and a reader following a shared link should see the same
	// disclosure.
	Italic bool
}

TextRun is a stretch of a verse that is uniformly Christ's words or is not — the exported shape of what every reading pane walks to colour a verse.

func RedLetterRuns

func RedLetterRuns(versionID string, v Verse) []TextRun

RedLetterRuns splits one verse of one translation into its red and not-red stretches, using that translation's OWN span table.

It exists because the web reader was the fifth rendering surface and the only one left behind when per-edition spans landed: it asked IsWordsOfChrist, which answers for the WEB and answers per VERSE. Two things were wrong on the page as a result. Mixed-speaker verses came out entirely red — John 4:9 put the Samaritan woman's words in Christ's colour — and every translation was rendered with the WEB's marks, so BSB and NKJV pages could not preserve their publishers' different editorial decisions.

versionID is the translation being rendered ("web", "bsb", "webc", "nkjv"). An unknown id returns black-letter runs. Red-letter decisions are editorial, so no edition is allowed to inherit another translation's marks.

The returned runs concatenate back to the verse's text exactly, so a caller can escape and emit each in turn without having to reason about offsets.

trimRuns is applied here, not left to the caller, because every pane in the app applies it (reading.go, android_chapter_html.go, reading_mobile_segments.go) and the page must not be the one surface that does not. It trims the verse AS A WHOLE — the old web code called strings.TrimSpace on the verse text, and trimming per run instead would glue "he said" onto the quotation beside it.

type TextSpan added in v1.2.7

type TextSpan struct {
	Start int `json:"start"`
	End   int `json:"end"`
}

Superscription is a chapter's Hebrew title — "For the choirmaster. According to Gittith. A Psalm of David." — part of the transmitted text of the Psalter (the Masoretic tradition numbers these lines as verse 1; the English versification leaves them unnumbered). Print sets them as an italic, unnumbered line between the psalm number and verse 1, and that is how the reading panes render them. Never in Verse.Text: the search indexes it as a verse-0 hit (SearchSmartLimited) and speech reads it as the read-along's verse-0 row (speechSegments, audio.go); share, copy and links still omit it, each its own decision (docs/SOURCE_FIELDS_DECISIONS.md). Its notes (the "Gittith is probably a musical term" glosses) join the chapter-bottom footnote section keyed "Title", ahead of the verse-keyed notes, under the same toggle. Heading is one of the publisher's section headings — "The Beatitudes", "The LORD Is My Shepherd", the speaker labels in the Song of Songs — or one of the other non-Scripture blocks an edition sets between verses: a major section head, a parallel-passage reference line, an acrostic letter.

It is editorial matter, added by the translators rather than translated, and it is kept apart from the text for that reason. Style is the publisher's own name for what this block is, so a surface can tell a section heading from an acrostic letter without guessing from the words. TextSpan is a half-open range of runes inside a verse's text: [Start, End). It is how the app records something TRUE OF PART OF A VERSE without putting anything into the verse itself — the words the translators supplied, today, and whatever else a publisher marks tomorrow.

type Verse

type Verse struct {
	BookName string // Name of the book (e.g., "John", "Genesis")
	Book     string // Abbreviated book name/reference
	Chapter  int    // Chapter number within the book
	Verse    int    // Verse number within the chapter
	Text     string // The actual text of the verse
	Search   string `json:"-"` // Lowercased text for fast case-insensitive search
	Ref      string `json:"-"` // Lowercased "book c:v" for fast reference matching

	// Footnotes is the translators' apparatus for this verse — alternate
	// renderings, manuscript variants, cross-references — captured at decode
	// time and carried SIDE-BAND, in the red-letter-runs mould. It is data,
	// not display: nothing here is rendered anywhere yet, and nothing here
	// may ever enter Text, the search index, the share pipeline, spoken
	// audio, or a link. Those pipelines all read Text, which stays
	// byte-identical whether footnotes are captured or not — the purity the
	// project's Revelation 22:18-19 standard requires, enforced by construction
	// and pinned by tests (footnotes_test.go). See docs/FOOTNOTES.md.
	Footnotes []Footnote `json:"footnotes,omitempty"`
	// ParaStart marks a verse that OPENS A PARAGRAPH in the publisher's own
	// setting of the text. Paragraphing is the translators' work, not
	// typesetting: it says where a speaker changes, a scene turns, or an
	// argument moves on, and committees decide it verse by verse. All three
	// editions carry it — the helloao feeds as chapter-level break nodes, the
	// API.Bible feed as paragraph blocks — and the app used to discard every
	// one of them and manufacture its own paragraphs from a character count,
	// which produced the same shape for three different translations and cut
	// the Nicodemus dialogue in the middle of a reply.
	//
	// It is structure, never text: Text stays byte-identical whether this is
	// set or not, so search, share, speech and links are untouched. The one
	// consumer is groupVersesIntoParagraphs, which every surface funnels
	// through, so setting this here reaches all of them at once.
	ParaStart bool `json:"para_start,omitempty"`

	// Supplied marks the words the TRANSLATORS SUPPLIED — what the King James
	// tradition sets in italics, added for English sense and standing in no
	// Hebrew or Greek word of the original. It is the edition's own disclosure
	// of where a translator made a judgement, and the app used to flatten it
	// away: roughly three thousand spans in the New Testament alone.
	//
	// Offsets into Text, never characters inside it, so the text a reader
	// searches, shares, copies, hears and links to is unchanged whether this
	// is set or not.
	Supplied []TextSpan `json:"supplied,omitempty"`

	// SmallCaps marks the words the edition sets in SMALL CAPITALS — above all
	// the divine name, where "Lord" in small capitals renders the Tetragrammaton
	// and "Lord" in ordinary case renders Adonai. The distinction is the
	// edition's, it is carried entirely by the letterforms, and the app used to
	// realise it by uppercasing the letters into the stored text, so a reader
	// who copied a verse received a spelling no edition prints.
	//
	// The publisher's own characters are kept instead, and this says where the
	// feature applies. The feed sends the span two ways — "Lord" with the
	// remainder in lower case, and a capital outside the span with the
	// remainder inside it as in "G" + "OD" — so a renderer asks for smcp and
	// c2sc together and the face resolves both correctly.
	//
	// Offsets into Text, never characters inside it.
	SmallCaps []TextSpan `json:"small_caps,omitempty"`

	// PoemLevels is the indent depth of each LINE of this verse, in order:
	// one entry per line of Text, zero where a line is not poetry. Hebrew
	// poetry is built of paired lines, and print sets the second half of a
	// pair indented under the first so the pairing can be seen; every edition
	// marks the depth and the app used to keep only the fact that a line was
	// poetry at all, so every line drew flush left.
	//
	// Lines, not characters: Text is unchanged whether this is set or not.
	PoemLevels []int `json:"poem_levels,omitempty"`
}

Verse represents a single verse (sentence/passage) from the Bible This is the smallest unit of text in our application

type VerseRef

type VerseRef struct {
	Book    string
	Chapter int
	Verse   int
}

VerseRef identifies a single verse. It is the anchor for annotations and the unit used when citing or copying for research.

func (VerseRef) String

func (r VerseRef) String() string

type VerseSpan

type VerseSpan struct {
	VersionID string
	Book      string
	Chapter   int
	Lo        int // first verse; 0 means the span is chapter-level
	Hi        int // inclusive last verse; 0 or < Lo means a single verse
}

VerseSpan is a location AND the numbering it is expressed in.

VersionID is not decoration and not optional. The whole reason a note can be shown under a translation it is not stored under is that MapVerse renumbers it; a span that does not say which translation numbers it that way cannot be renumbered later, and cannot be checked against the translation on screen.

type WebReaderPalette

type WebReaderPalette struct {
	Background       color.NRGBA
	Surface          color.NRGBA
	Text             color.NRGBA
	TextMuted        color.NRGBA
	Accent           color.NRGBA
	Border           color.NRGBA
	VerseNumber      color.NRGBA
	RedLetter        color.NRGBA
	Highlight        color.NRGBA
	ControlHover     color.NRGBA
	ControlSelection color.NRGBA
}

WebReaderPalette is the subset of the app palette used by the static reader. Its translucent control colours reuse the Fyne theme's hover and selection tints, while the separate opaque highlight remains reserved for scripture.

func WebReaderPalettes

func WebReaderPalettes() (light, dark WebReaderPalette)

WebReaderPalettes returns the light and dark static-reader palettes. Keeping this seam beside the decoder and tint exports lets cmd/websitegen derive its CSS from the app palette instead of maintaining another set of colour values.

Source Files

Directories

Path Synopsis
cmd
bibletext command
Command bibletext is the macOS / Windows / Linux entry point for the BibleText reader.
Command bibletext is the macOS / Windows / Linux entry point for the BibleText reader.
linuxmeta command
Command linuxmeta renders the Linux packaging inputs from one listing source, the way cmd/msstore renders the Windows package inputs: the AppStream MetaInfo (a keyed variant for the snap and the AppImage, a keyless one for Flathub), the desktop entries, the icons, snap/snapcraft.yaml and the Flatpak manifest.
Command linuxmeta renders the Linux packaging inputs from one listing source, the way cmd/msstore renders the Windows package inputs: the AppStream MetaInfo (a keyed variant for the snap and the AppImage, a keyless one for Flathub), the desktop entries, the icons, snap/snapcraft.yaml and the Flatpak manifest.
mobile command
Command mobile is the iOS (and Android) entry point for the BibleText reader.
Command mobile is the iOS (and Android) entry point for the BibleText reader.
msstore command
Command msstore prepares the inputs of the Microsoft Store package: the tile assets scaled from the shipped icon (cmd/bibletext/Icon.png, the mark every other channel carries), and the AppxManifest filled from the desktop ledger (cmd/bibletext/FyneApp.toml) and the identity Partner Center assigned when the name was reserved (msstore/identity.json).
Command msstore prepares the inputs of the Microsoft Store package: the tile assets scaled from the shipped icon (cmd/bibletext/Icon.png, the mark every other channel carries), and the AppxManifest filled from the desktop ledger (cmd/bibletext/FyneApp.toml) and the identity Partner Center assigned when the name was reserved (msstore/identity.json).
sitepages command
Command sitepages renders the hand-written root pages that are published alongside the generated web reader.
Command sitepages renders the hand-written root pages that are published alongside the generated web reader.
websitegen command
Command websitegen builds the static web reader published at bibletext.co.uk — the destination of the app's "Share as link".
Command websitegen builds the static web reader published at bibletext.co.uk — the destination of the app's "Share as link".

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL