diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index f502d382..5b86865a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -8,6 +8,18 @@ body: value: | Thanks for taking the time to report this bug! Please fill out the details below. + - type: dropdown + id: device-type + attributes: + label: Device + description: Which device is this affecting? + options: + - X4 + - X3 + - Both + validations: + required: true + - type: input id: version attributes: diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index ac299f43..3e95c63e 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,3 +1,5 @@ ## Summary -- **What changes are included?** +### **What changes are included?** + + diff --git a/.github/workflows/release-fonts.yml b/.github/workflows/release-fonts.yml new file mode 100644 index 00000000..c95c0927 --- /dev/null +++ b/.github/workflows/release-fonts.yml @@ -0,0 +1,106 @@ +name: Build & Publish SD Card Fonts + +# Fonts change rarely — run manually when font sources or the conversion +# pipeline are updated. Publishes .cpfont files + fonts.json manifest as +# GitHub Release assets on the crosspoint-fonts repo so font releases don't +# clutter the firmware releases page. +# +# Requires a repository secret FONTS_REPO_TOKEN — a fine-grained PAT (or +# classic PAT) with contents:write permission on the target fonts repo. +on: + workflow_dispatch: + +env: + FONTS_REPO: crosspoint-reader/crosspoint-fonts + +jobs: + build-fonts: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + with: + submodules: recursive + + - uses: actions/setup-python@v6 + with: + python-version: '3.14' + + - name: Install font tools + run: pip install freetype-py fonttools pyyaml + + - name: Install system dependencies + run: sudo apt-get update && sudo apt-get install -y libfreetype6-dev + + - name: Read version constants + id: versions + run: | + cd lib/EpdFont/scripts + echo "binary=$(python3 -c 'from cpfont_version import CPFONT_VERSION; print(CPFONT_VERSION)')" >> "$GITHUB_OUTPUT" + echo "metadata=$(python3 -c 'from cpfont_version import FONTS_MANIFEST_VERSION; print(FONTS_MANIFEST_VERSION)')" >> "$GITHUB_OUTPUT" + + - name: Build SD card fonts + run: python3 lib/EpdFont/scripts/build-sd-fonts.py --clean --verbose -j 1 + + - name: Flatten output for release assets + run: | + mkdir -p dist + find lib/EpdFont/scripts/output -name '*.cpfont' -exec cp {} dist/ \; + + - name: Compute release tags + id: tags + env: + GH_TOKEN: ${{ secrets.FONTS_REPO_TOKEN }} + run: | + BASE="sd-fonts-m${{ steps.versions.outputs.metadata }}-b${{ steps.versions.outputs.binary }}" + echo "base=$BASE" >> "$GITHUB_OUTPUT" + + # Find the highest existing revision for this m/b pair + LAST=$(gh release list --repo "${{ env.FONTS_REPO }}" \ + --json tagName --jq \ + '[.[] | select(.tagName | startswith("'"${BASE}-r"'")) | .tagName | split("-r")[1] | tonumber] | max // 0') + NEXT=$((LAST + 1)) + echo "revision=$NEXT" >> "$GITHUB_OUTPUT" + echo "versioned=${BASE}-r${NEXT}" >> "$GITHUB_OUTPUT" + + - name: Generate manifest + run: | + python3 scripts/generate-font-manifest.py \ + --input dist \ + --base-url "https://github.com/${{ env.FONTS_REPO }}/releases/download/${{ steps.tags.outputs.base }}/" \ + --output dist/fonts.json \ + --descriptions-from lib/EpdFont/scripts/sd-fonts.yaml + + - name: Publish versioned release to fonts repo + env: + GH_TOKEN: ${{ secrets.FONTS_REPO_TOKEN }} + run: | + VERSIONED="${{ steps.tags.outputs.versioned }}" + TITLE="SD Card Fonts (${VERSIONED#sd-fonts-})" + + gh release create "$VERSIONED" dist/* \ + --repo "${{ env.FONTS_REPO }}" \ + --title "$TITLE" \ + --notes "Pre-built \`.cpfont\` font files for CrossPoint Reader. + + Download individual files or use **Settings > System > Manage Fonts** on the device. + + See [SD Card Fonts documentation](https://github.com/${{ github.repository }}/blob/main/docs/sd-card-fonts.md) for details." + + - name: Update stable tag for device downloads + env: + GH_TOKEN: ${{ secrets.FONTS_REPO_TOKEN }} + run: | + BASE="${{ steps.tags.outputs.base }}" + + # Delete the old stable release for this m/b pair (the versioned releases are kept) + gh release delete "$BASE" --repo "${{ env.FONTS_REPO }}" --yes 2>/dev/null || true + + gh release create "$BASE" dist/* \ + --repo "${{ env.FONTS_REPO }}" \ + --title "SD Card Fonts (${BASE#sd-fonts-})" \ + --notes "Current font build for manifest v${{ steps.versions.outputs.metadata }}, binary format v${{ steps.versions.outputs.binary }}. Devices with this firmware version download from this release. + + This is revision **${{ steps.tags.outputs.revision }}** — see [\`${{ steps.tags.outputs.versioned }}\`](https://github.com/${{ env.FONTS_REPO }}/releases/tag/${{ steps.tags.outputs.versioned }}) for the immutable copy. + + Download individual files or use **Settings > System > Manage fonts** on the device." diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7440aeb2..3957b19d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,12 +3,12 @@ on: workflow_dispatch: inputs: version: - description: "Release version, without the leading v. Example: 1.2.9.1" + description: "Compile release builds" required: true type: string push: tags: - - "v*" + - 'v[0-9]+.[0-9]+.[0-9]+' permissions: contents: write @@ -20,6 +20,14 @@ jobs: - uses: actions/checkout@v6 with: submodules: recursive + fetch-depth: 0 + + - name: Verify release tag is on main + if: github.event_name == 'push' + run: | + git fetch origin main:refs/remotes/origin/main + TAG_COMMIT="$(git rev-list -n 1 "$GITHUB_REF_NAME")" + git merge-base --is-ancestor "$TAG_COMMIT" origin/main - uses: actions/setup-python@v6 with: @@ -53,26 +61,47 @@ jobs: echo "RELEASE_VERSION=$VERSION" >> "$GITHUB_ENV" sed -i "s/crossink_version = .*/crossink_version = $VERSION/" platformio.ini - - name: Build CrossInk-Carousel + - name: Build CrossPoint env: CROSSPOINT_RELEASE_VERSION: ${{ env.RELEASE_VERSION }} run: pio run -e tiny -e xlarge -e no_emoji - - name: Rename binaries with carousel prefix + - name: Generate release catalog + run: | + python3 scripts/generate_release_catalog.py \ + --version "${{ env.RELEASE_VERSION }}" \ + --repo "${{ github.repository }}" \ + --firmware ".pio/build/tiny/firmware-tiny-v${{ env.RELEASE_VERSION }}.bin" \ + --firmware ".pio/build/xlarge/firmware-xlarge-v${{ env.RELEASE_VERSION }}.bin" \ + --firmware ".pio/build/no_emoji/firmware-no_emoji-v${{ env.RELEASE_VERSION }}.bin" \ + --output ".pio/build/catalog" + + - name: Publish catalog to GitHub Pages source + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} run: | - for v in tiny xlarge no_emoji; do - cp ".pio/build/$v/firmware-$v-v${RELEASE_VERSION}.bin" \ - ".pio/build/$v/carousel-firmware-$v-v${RELEASE_VERSION}.bin" - done + git config user.name "uxjulia" + git config user.email "julia@uxj.io" + git fetch origin "$DEFAULT_BRANCH" + git worktree add ../crossink-pages "origin/$DEFAULT_BRANCH" + cp ".pio/build/catalog" "../crossink-pages/docs/catalog" + cd ../crossink-pages + if git diff --quiet -- docs/catalog; then + echo "docs/catalog is already current" + exit 0 + fi + git add docs/catalog + git commit -m "Update release catalog for v${RELEASE_VERSION}" + git push origin "HEAD:$DEFAULT_BRANCH" - name: Create GitHub Release uses: softprops/action-gh-release@v3 with: + token: ${{ secrets.RELEASE_PAT }} tag_name: v${{ env.RELEASE_VERSION }} target_commitish: ${{ github.sha }} draft: true - generate_release_notes: true files: | - .pio/build/tiny/carousel-firmware-tiny-v${{ env.RELEASE_VERSION }}.bin - .pio/build/xlarge/carousel-firmware-xlarge-v${{ env.RELEASE_VERSION }}.bin - .pio/build/no_emoji/carousel-firmware-no_emoji-v${{ env.RELEASE_VERSION }}.bin + .pio/build/tiny/firmware-tiny-v${{ env.RELEASE_VERSION }}.bin + .pio/build/xlarge/firmware-xlarge-v${{ env.RELEASE_VERSION }}.bin + .pio/build/no_emoji/firmware-no_emoji-v${{ env.RELEASE_VERSION }}.bin diff --git a/.github/workflows/release_candidate.yml b/.github/workflows/release_candidate.yml index 9da43548..3d5ab853 100644 --- a/.github/workflows/release_candidate.yml +++ b/.github/workflows/release_candidate.yml @@ -93,6 +93,7 @@ jobs: - name: Create GitHub RC Prerelease uses: softprops/action-gh-release@v3 with: + token: ${{ secrets.RELEASE_PAT }} tag_name: ${{ env.RC_TAG }} target_commitish: ${{ github.sha }} name: RC ${{ env.RC_BRANCH_SAFE }} ${{ env.RC_HASH_SHORT }} diff --git a/.gitignore b/.gitignore index e861e414..0a5a03ee 100644 --- a/.gitignore +++ b/.gitignore @@ -19,4 +19,8 @@ fs_/ releases/ # scratchpad used as release notes for next crossink version -release.md \ No newline at end of file +release.md +*.cpfont +lib/EpdFont/scripts/downloaded_fonts/ +lib/EpdFont/scripts/instanced_fonts/ +lib/EpdFont/scripts/output/ diff --git a/.gitmodules b/.gitmodules index b0d8e240..80308f05 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "open-x4-sdk"] path = open-x4-sdk - url = https://github.com/open-x4-epaper/community-sdk.git + url = https://github.com/crosspoint-reader/community-sdk.git diff --git a/AGENTS.md b/AGENTS.md index 2ac669e5..eaf1bde1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -37,6 +37,7 @@ Project: Open-source e-reader firmware for Xteink X4 (ESP32-C3). 6. Prefer `constexpr` for compile-time constants. 7. Reserve `std::vector` capacity before push loops. 8. Debounce persistent writes. Do not write progress on every page turn. +9. `new` is not nothrow on ESP32. With exceptions disabled, bare `new` calls `abort()` on allocation failure instead of returning `nullptr`. Use `new (std::nothrow)` or `makeUniqueNoThrow()` from `lib/Memory/Memory.h` for fallible allocations. ## HAL And Platform Rules @@ -84,11 +85,21 @@ Project: Open-source e-reader firmware for Xteink X4 (ESP32-C3). ## Changelog -When new features are added or issues are fixed, make sure to add an entry to `CHANGELOG.md`. Types of changes should have their own section: +When new features are added or issues are fixed, make sure to add an entry to `CHANGELOG.md` with the user-facing description of the change. Types of changes should have their own section. -Added - for new features. -Changed - for changes in existing functionality. -Deprecated - for soon-to-be removed features. -Removed - for now removed features. -Fixed - for any bug fixes. -Security - in case of vulnerabilities. \ No newline at end of file +### Changelog Guiding Principles + +- Changelogs are _for humans_, not machines. +- There should be an entry for every single version. +- The same types of changes should be grouped. +- Versions and sections should be linkable. +- The latest version comes first. +- The release date of each version is displayed. + +### Types of Changelog Changes +- Added - for new features. +- Changed - for changes in existing functionality. +- Deprecated - for soon-to-be removed features. +- Removed - for now removed features. +- Fixed - for any bug fixes. +- Security - in case of vulnerabilities. diff --git a/CHANGELOG.md b/CHANGELOG.md index 378990b5..bb969fde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,20 +2,98 @@ ## [Unreleased] -### Added -- New "Tap Power While Asleep to Cycle" display setting. When on, a brief power-button tap from sleep picks a fresh random image from `/.sleep` and re-enters deep sleep, instead of waking. Off by default — each cycle costs a boot + e-ink half-refresh worth of battery. Pinned sleep images are skipped in cycle mode (always random) +## [flexble-v1.0.0] - 2026-05-17 +First stable FlexBLE release post-upstream-merge. FlexBLE now uses its own semver (`flexble_version`) independent of upstream CrossInk's versioning. The `crossink_version` field continues to track the upstream sync point (currently 1.2.11.1). + +### Added (FlexBLE fork) - BLE HID page-turner support. From inside a book, open the reader menu and select **Bluetooth** to pair a Bluetooth remote (e.g. IINE GameBrick) and use its buttons as virtual page-turn keys. Reader-session-only: BLE auto-disables when you exit the book. Pairing is remembered across reboots; enable Bluetooth in a later reader session to auto-reconnect. +- **BT Quick Connect** action in the Global Book Settings drawer. From inside a book, long-press the menu button → scroll to "BT Quick Connect" → activates BLE and reconnects to the last bonded remote (or launches the pairing UI if no remote is saved). Closes the drawer back to the book on success. +- After a successful BT connection from the in-book Bluetooth menu, FlexBLE now auto-pops both the BT settings and the reader menu so the user lands straight back in the book — no manual back-tapping required. +- New "Tap Power While Asleep to Cycle" display setting. When on, a brief power-button tap from sleep picks a fresh random image from `/.sleep` and re-enters deep sleep, instead of waking. Off by default — each cycle costs a boot + e-ink half-refresh worth of battery. Pinned sleep images are skipped in cycle mode (always random). - `.png` sleep images (including transparency) are now accepted in **Custom** Sleep Screen mode, not just Page Overlay. Transparent regions compose over the last reader page so a transparent PNG sleep screen reveals book text underneath. On the deep-sleep cycle path (tap-power-while-asleep), the last reader page is restored from a small SD-cached snapshot written when leaving a book. - **Global Book Settings** drawer. Long-press the menu button in a book to pop up a bottom-drawer-style quick-settings panel with a "Global Book Settings" tab on top. Lists every reader setting (font, layout, hyphenation, bionic, images, etc.) and uses e-ink fast refresh so toggling is snappy. Closing the drawer re-flows the page immediately when a setting was actually changed; a no-op visit skips the re-layout. Triggered via the existing `longPressMenuAction` setting (default flipped to the new "Book Settings" option). Architecture adapted from [inx by Dave Allie](https://github.com/obijuankenobiii/inx) (MIT). +- **PackBits-compressed BW backup** for the grayscale anti-aliasing pass. Replaces the chunked 12 × 4 KB lazy allocation with a single 16-32 KB worst-case bounded buffer. Typical reader pages compress to 2-5 KB, dramatically reducing the fragmentation pressure that caused grayscale to fail when BLE was active. +- Auto-retry on chapter-layout abort: if the parser trips the low-heap floor while BLE is consuming its ~58 KB share, FlexBLE silently drops BLE, retries the layout (now with the extra headroom), and lets the existing auto-reconnect logic re-establish the link on the user's next remote button press. -### Changed +### Changed (FlexBLE fork) +- Bluetooth main menu now uses Up/Down labels and binds both side U/D and front L/R to navigation, matching Reader Options and Controls Options conventions. +- LYRA_FLOW (5-book carousel) preserved as a first-class theme option through the upstream merge. Front Prev/Next iterates within the carousel only; side Up/Down jumps to the menu list and iterates through it, with the carousel staying pinned to the last selected book. - Reader setting labels dropped the redundant "Reader" prefix ("Font Family", "Font Size", "Line Spacing", "Screen Margin", "Paragraph Alignment") so they fit better in the drawer. +- Default `tiny` build now omits the Teensy (8px) font variant to make room for the NimBLE-Arduino BLE stack. Re-enable by removing `OMIT_TEENSY_FONT` from `platformio.ini` if you don't need Bluetooth. +- EPUB layout heap thresholds relaxed for BLE-paired sessions: text-layout floor 48 → 16 KB free, image-extraction floor 72 → 56 KB free. Brings parser within reach of the heap remaining after NimBLE allocations; failures still degrade gracefully (image extraction falls back to "page rendered without images"; layout aborts trigger the new BLE-disable-and-retry path). +- BookSettingsDrawer hint area enlarged so the bottom button labels render fully (were clipped against the screen edge at the previous height). +- Settings → System now displays `FlexBLE x.y.z (CrossInk x.y.z.z)` so both the fork and upstream sync version are visible. +- Merged upstream CrossInk v1.2.10 and v1.2.11/v1.2.11.1 (see entries below for what those releases brought). + +### Fixed (FlexBLE fork) +- BLE page-turner could hang at chapter boundaries when a release HID frame was dropped (NimBLE task starvation during the heavy section-load render is a common trigger). Subsequent presses were silently filtered out by both the BLE-side `activeInjectedButton` gate and HalGPIO's same-state short-circuit, requiring a book exit/re-enter to recover. Added two backstops: a 2 s max-hold auto-release on virtual buttons (suppressed release edge so no phantom page turn fires), and a force-release of a stuck injection in Game Brick's same-key re-press promotion path. +- BluetoothSettings used `wasPressed(Confirm)` for action triggers instead of `wasReleased`, causing the Confirm release to leak through pop transitions and re-launch the BT menu from the reader menu underneath. All five action handlers now consistently use `wasReleased`. +- Reader exit during BLE reconnect: tapping Back impatiently during the 2-3 s connect freeze used to leak the release through to the reader's home-on-back-release handler, exiting the book and auto-disabling BLE. The next Back release is now suppressed when `checkAutoReconnect` blocked for >500 ms. +- Flow theme home carousel input grammar fixed across the merge — front Prev/Next stays within the carousel, side U/D moves through menu items without dragging the carousel along. + +## [v1.2.11.1] - 2026-05-15 ### Changed -- Default `tiny` build now omits the Teensy (8px) font variant to make room for the NimBLE-Arduino BLE stack. Re-enable by removing `OMIT_TEENSY_FONT` from `platformio.ini` if you don't need Bluetooth. +- Removed Medium font size from `xlarge` build to get it below the size limit ### Fixed -- BLE page-turner could hang at chapter boundaries when a release HID frame was dropped (NimBLE task starvation during the heavy section-load render is a common trigger). Subsequent presses were silently filtered out by both the BLE-side `activeInjectedButton` gate and HalGPIO's same-state short-circuit, requiring a book exit/re-enter to recover. Added two backstops: a 2 s max-hold auto-release on virtual buttons (suppressed release edge so no phantom page turn fires), and a force-release of a stuck injection in Game Brick's same-key re-press promotion path. +- Included Lyra Carousel by activating the build flag `DCROSSINK_ENABLE_LYRA_CAROUSEL=1` + +## [v1.2.11] - 2026-05-14 + +### Added +- Added new personal theme: "Minimal" +- Added a custom sleep timer picker so `Time to Sleep` can be set from 1 to 30 minutes instead of cycling fixed presets. +- Added an in-reader Controls shortcut so you can customize your buttons without leaving the book. +- Added bookmark cleanup shortcuts: hold Select on a bookmark to delete it, or hold Open on a book in Bookmarks to clear that book's bookmark list. +- Added a confirmation message after deleting a book's cache from the reader or File Browser. +- Added a File Browser long-press action for deleting an EPUB or XTC book's cache +- Added a File Browser long-press action for marking EPUB books as finished or unfinished. + +### Changed +- Hardened deep sleep entry by shutting WiFi down before waiting for the power button to be released. +- Raised the web file-transfer filename limit from 100 to 150 bytes so longer uploaded filenames are preserved. +- Made the in-reader Reader Options menu include the same Reader settings and actions as Settings > Reader. + +### Fixed +- Fixed inline EPUB images disappearing in landscape when their bottom edge slightly overlaps the screen margin. +- Reduced unnecessary low-memory image suppression for JPEG-heavy EPUB chapters and added CSS heap diagnostics during chapter rebuilds. +- Allowed wider inline JPEG images in EPUBs to render when they still fit the total pixel and heap safety limits. +- Fixed the SD-card font picker reopening immediately after selecting a font from Settings > Reader > Font Family. +- Fixed in-reader font-size changes for SD card fonts not working +- Fixed in-reader SD-card font changes not always rebuilding the current EPUB page layout. + +## [v1.2.10] - 2026-05-11 + +### Added +- Added a `Recent Books View` setting so the dedicated Recent Books screen can switch between the classic list and a 3x3 cover grid. +- Added more flexible reader controls, including orientation-aware front/side button settings, nav-only or all-button front inversion, tilt page turn shortcuts, and side-button long-press rotation actions. +- Added a per-session auto page turn interval picker with values from 5 to 120 seconds. +- Added a file-browser Home/Back long-press action for toggling hidden files and folders. +- Added EPUB rendering and diagnostics improvements, including visible `
` separators and heap logs around section rebuilds, image extraction, page serialization, and sleep-cache rebuilds. +- Added reader font coverage for block redactions, black-square ornaments, Greek category letters, and turned-comma punctuation (PR #104). +- Added simulator tools for testing sleep/wake behavior and smoke-testing common screens and EPUB reader menus. + +### Changed +- Reduced Controls settings section spacing so the grouped controls fit better on X3 screens. +- Made front reader long-press actions trigger when the hold delay is reached while normal page turns still trigger on release. + +- Used the fast EPUB spine/TOC indexing path for books with 300+ spine entries so heavily split books build `book.bin` faster on first open. +- Allowed the web file manager and WebDAV to browse dot-prefixed hidden files when hidden files are enabled, matching the device file browser. + +### Fixed +- Fixed reader button and shortcut behavior, including X3 power-button wake filtering, folder delete long-press timing, and WiFi scan/connect screens that could not be exited while work was in progress. +- Fixed RoundedRaff home-menu, keyboard, and button-hint rendering issues so Settings remains reachable and compact labels no longer overlap or disappear. +- Fixed font and glyph handling by reducing persistent SD-card font advance-cache memory, releasing optional font caches before image extraction only when heap is tight, and showing a visible replacement symbol when compact UI fonts lack `U+FFFD`. +- Fixed KOReader Sync authentication diagnostics and an in-reader sync crash, including clearer handling when a server or proxy returns non-JSON content. +- Fixed EPUB text rendering for redactions, whitespace-only XHTML text nodes, simple black CSS span backgrounds, list bullets in `
  • ...

  • ` items, and very long base64-like text runs. +- Fixed EPUB image, thumbnail, and section-rebuild stability so image-heavy chapters use less temporary memory, scale images more reliably, avoid stale dimensions, and suppress optional image work earlier under heap pressure. +- Fixed EPUB low-memory and cache safety by skipping optional next-chapter indexing and sleep-page cache rebuilds when heap is tight, failing safely with a malformed-book warning and Home exit path, rebuilding incompatible fork-written caches, and handling low-memory CSS parsing, truncated SD writes, invalid serialized strings, and failed temp-cache promotion. +- Fixed a Home crash after clearing reading cache by skipping optional EPUB thumbnail rebuilds when the source EPUB cache is missing. +- Fixed reader prewarm behavior by skipping image decoding, keeping mixed-style font glyphs cached together, and avoiding section rebuilds for render-quality-only option changes. +- Fixed concurrent render/storage crashes by serializing `GfxRenderer` scratch-buffer access, shared SPI bus access, and failed SPI lock cleanup. +- Fixed Recent Books, EPUB/XTC thumbnail caches, deleted-folder metadata, and XTC cover scaling so cached book data stays in sync and grid covers fill their slots correctly. +- Fixed simulator build configuration so SDL2 and simulator-provided network/OTA shims compile cleanly. ## [v1.2.9.1] - 2026-05-03 @@ -23,10 +101,7 @@ - Cleaned up EPUB table rendering by removing synthetic row/cell labels and defaulting table cells to readable left alignment - Allow simple EPUB tables with full-width note rows so a single `colspan` cell spanning the whole table no longer forces the entire table back to paragraph fallback -### Added -- Short-press power button acts as confirm within the in-reader menus - ### Fixed - Fix power-button shortcut conflicts outside the reader so reader-only actions fall back to `Confirm` while Sleep, Refresh, Screenshot, Sync Progress, and File Transfer remain real power actions. Those that had short-press power button to act as sleep saw unstable behavior previously. This should be fixed now - Fix a potential crash when using `Go to %` in EPUBs -- Fix a potential crash when entering sleep with Page Overlay enabled if the cached EPUB page data is invalid \ No newline at end of file +- Fix a potential crash when entering sleep with Page Overlay enabled if the cached EPUB page data is invalid diff --git a/README.md b/README.md index 7f45f072..0e97b23a 100644 --- a/README.md +++ b/README.md @@ -55,10 +55,13 @@ My goal with this fork was to maintain the core Crosspoint firmware while integr ### Highlights - New reader fonts: ChareInk, Lexend Deca, and Bitter -- Unicode emoji and miscellaneous symbols support +- Unicode emoji and miscellaneous symbols support (a limited subset) - Adjusted font sizes: Teensy (8pt), Tiny (10pt), Small (12pt), Medium (14pt), Large (16pt), Extra Large (18pt), Huge (20pt). See [Font Sizes](#font-sizes) for more details. - Added ~~strikethrough~~ support - Made underlines thicker for better visibility +- Added support for `
    ` section breaks +- Added support for "redaction" style rendering +- Added improved support for tables with simple markup - Added ability to add bookmarks - Added ability to remap front buttons that only applies in the reader - Added Bionic Reading and Guide Dots as optional reader modes @@ -70,10 +73,11 @@ My goal with this fork was to maintain the core Crosspoint firmware while integr - To turn this on, go to Settings > System > Move finished books to Read folder. Once a book is marked as finished, the book will be moved to the folder when the book is closed. - In-book menu to quickly adjust reader options without having to exit the book - Reading stats: total books read, total reading time, number of sessions, pages turned, average session time, pages turned per minute. You can also set your reading stats as your sleep screen. -- Changed label for "Auto Turn (Pages Per Minute)" to "Auto Page Turn Interval (seconds)" - - Added additional page turn intervals (how many seconds pass between page turns). Options are now (in seconds): 60, 45, 30, 20, 15, 10, 5, OFF. -- Added Vietnamese language support +- Added customizable Auto Page Turn Interval (anything between 5-120 seconds) +- Added ability to view Recent Books as a 3x3 grid view +- Added ability to install custom fonts on the SD card - Device simulator during development +- To view a more detailed list for each version, visit the [releases](https://github.com/uxjulia/CrossInk/releases) page to read release notes. --- @@ -87,6 +91,18 @@ The default fonts have been replaced with ChareInk, Lexend Deca, and Bitter. The The UI now uses [Inter](https://fonts.google.com/specimen/Inter) as the display font which has improved readability at smaller sizes. +### Wireless workflows + +- File transfer web UI +- EPUB Optimizer +- Web settings UI/API (edit many device settings from browser) +- WebSocket fast uploads +- WebDAV handler +- AP mode (hotspot) and STA mode (join existing WiFi), both with QR helpers +- Calibre wireless connect flow +- OPDS browser with saved servers (up to 8), search, pagination, and direct download +- OTA update checks and installs from GitHub releases + ### Emojis and Misc Glyphs - Support for a limited set of Unicode [Emoticons](https://unicode-explorer.com/b/1F600) and [Miscellaneous Symbols](https://unicode-explorer.com/b/2600) using [Noto Emoji](https://fonts.google.com/noto/specimen/Noto+Emoji) and [Noto Sans Symbols](https://fonts.google.com/noto/specimen/Noto+Sans+Symbols) font. @@ -114,19 +130,17 @@ There are 3 available build variants to choose from due to build size constraint > Teensy, Tiny, and Small font sizes had to be removed to reduce build size and still support emoji/symbols. - Emoji & Misc. Symbols Support -- 4 Font sizes: - - Medium (14pt) +- 3 Font sizes: - Large (16pt) - Extra Large (18pt) - Huge (20pt) **no_emoji** -> All standard font sizes through Extra Large are available, but no emoji/symbols support. +> All standard font sizes from Tiny through Extra Large are available, but no emoji/symbols support. - **No** Emoji & Misc. Symbols Support -- 6 Font sizes: - - Teensy (8pt) +- 5 Font sizes: - Tiny (10pt) - Small (12pt) - Medium (14pt) @@ -196,6 +210,7 @@ Map the **Power** or **Menu** button short/long-press action to one of the follo - Take Screenshot (takes a screenshot) - Auto Page Turn (cycles through the page turn intervals: **Off → 5s → 10s → 15s → 20s → 30s → 45s → 60s → Off →**) - File Transfer (opens the File Transfer menu) +- Tilt Page Turn (turns tilt-based page turning on/off on supported devices) ### Reading stats @@ -266,7 +281,9 @@ pio run -e simulator --- -## Installing +### Localization + +CrossPoint includes 22 UI languages and counting. ### Web @@ -278,7 +295,52 @@ pio run -e simulator To revert back to the official firmware, you can flash the latest official firmware from https://crosspointreader.com/#flash-tools -### Command line (specific firmware version) +--- + +## USB-locked devices (Xteink Unlocker) + +Some Xteink units purchased from third-party stores (e.g. AliExpress) ship with USB flashing locked from the factory. +If your device is locked, you will need to use the **Xteink Unlocker** tool available at +https://crosspointreader.com/#unlock-tool before you can flash CrossPoint. + +**You do not need this tool if you bought your device directly from xteink.com.** Those units are not locked. + +**Not sure if your device is locked?** Power it on, connect the USB-C cable, and try flashing via the web flasher first (see +[Install firmware](#install-firmware) below). If the browser's serial device picker does not show your device, try a different +USB port or browser before assuming the device is locked. Only reach for the unlocker if the device still doesn't appear. + +### ⚠️ WARNING: READ THIS BEFORE USING THE UNLOCKER ⚠️ + +**The only officially supported firmwares in the unlock tool are CrossPoint and CrossInk.** + +Flashing any other firmware on a USB-locked device may **permanently brick the device** or leave it **permanently +stuck on that firmware with no recovery path**. Once USB flashing is re-locked, your only way back is via OTA, and if +the firmware you flashed doesn't support OTA, **there is no way out**. + +**The Papyrix fork has removed OTA update support from its code.** If you flash Papyrix onto a +USB-locked unit, you will have **zero update or recovery path** and will be stuck on it forever. **Do not flash +Papyrix (or any other unsupported firmware) on a locked device.** + +## Install firmware + +### Web installer (recommended) + +1. Connect your device to your computer via USB-C and wake/unlock the device +2. Go to https://crosspointreader.com/#flash-tools, select device (X3 or X4), and choose an official CrossPoint release. + +### Web installer (specific version) + +1. Connect your device to your computer via USB-C and wake/unlock the device +2. Download a `firmware.bin` from [Releases](https://github.com/crosspoint-reader/crosspoint-reader/releases), local build, or continuous integration artifact. +3. Go to https://crosspointreader.com/#flash-tools, select device (X3 or X4), click "Custom .bin" and upload a `firmware.bin`. + +### Revert to Official Firmware + +To revert to the official firmware, you can also flash the latest official firmware using https://crosspointreader.com/#flash-tools. + +### Command line + +1. Install [`esptool`](https://github.com/espressif/esptool): > **Note:** These instructions are for macOS and Linux. Windows users should use the [Web installer](#web) instead. @@ -306,7 +368,19 @@ esptool.py --chip esp32c3 --port /dev/cu.usbmodem2101 --baud 921600 write_flash # Swap the filename for the variant (tiny / xlarge / no_emoji) and version you downloaded. ``` -## Development +--- + +## Documentation + +- [User Guide](./USER_GUIDE.md) +- [Web server usage](./docs/webserver.md) +- [Web server endpoints](./docs/webserver-endpoints.md) +- [Project scope](./SCOPE.md) +- [Contributing docs](./docs/contributing/README.md) + +--- + +## Development quick start ### Prerequisites @@ -315,18 +389,18 @@ esptool.py --chip esp32c3 --port /dev/cu.usbmodem2101 --baud 921600 write_flash - USB-C cable for flashing the ESP32-C3 - Xteink X4 -### Checking out the code +### Setup CrossPoint uses PlatformIO for building and flashing the firmware. To get started, clone the repository: ``` git clone --recursive https://github.com/chintanvajariya/CrossInk-Carousel -# Or, if you've already cloned without --recursive: +# if cloned without --recursive: git submodule update --init --recursive ``` -### Flashing your device +### Build / flash / monitor Connect your Xteink X4 to your computer via USB-C and run the following command. Replace `tiny` with `xlarge` or `no_emoji` if you prefer a different build variant (see [Font Sizes](#font-sizes)). @@ -344,7 +418,7 @@ First, make sure all required Python packages are installed: python3 -m pip install pyserial colorama matplotlib ``` -after that run the script: +After that run the script: ```sh # For Linux @@ -357,6 +431,8 @@ python3 scripts/debugging_monitor.py /dev/cu.usbmodem2101 Minor adjustments may be required for Windows. +--- + ## Internals The firmware is pretty aggressive about caching data down to the SD card to minimise RAM usage. The ESP32-C3 only @@ -379,8 +455,6 @@ cache. This cache directory exists at `.crosspoint` on the SD card. The structur │ ├── 0.bin # Chapter data (screen count, all text layout info, etc.) │ ├── 1.bin # files are named by their index in the spine │ └── ... -│ -└── epub_189013891/ ``` Deleting the `.crosspoint` directory will clear the entire cache. diff --git a/USER_GUIDE.md b/USER_GUIDE.md index f565b720..82719cf8 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -23,6 +23,8 @@ Welcome to the **CrossInk** firmware. This guide outlines the hardware controls, - [3.6.5 OPDS Servers (Multiple Libraries)](#365-opds-servers-multiple-libraries) - [3.6.6 Web Settings (WiFi + OPDS)](#366-web-settings-wifi--opds) - [3.6.7 KOReader Sync Quick Setup](#367-koreader-sync-quick-setup) + - [3.6.6 Web Settings (WiFi + OPDS)](#366-web-settings-wifi--opds) + - [3.6.7 KOReader Sync Quick Setup](#367-koreader-sync-quick-setup) - [3.7 Sleep Screen](#37-sleep-screen) - [4. Reading Mode](#4-reading-mode) - [Page Turning](#page-turning) @@ -196,7 +198,7 @@ The Settings screen allows you to configure the device's behavior. There are a f - **WiFi Networks**: Connect to WiFi networks for file transfers and firmware updates. - **KOReader Sync**: Options for setting up KOReader for syncing book progress. -- **OPDS Servers**: Manage one or more OPDS libraries for browsing and downloading books. See [OPDS Servers (Multiple Libraries)](#365-opds-servers-multiple-libraries) below. +- **OPDS Servers**: Manage one or more OPDS [(Open Publication Distribution System)](https://en.wikipedia.org/wiki/Open_Publication_Distribution_System) libraries for browsing and downloading books. See [OPDS Servers (Multiple Libraries)](#365-opds-servers-multiple-libraries) below. - **Clear Reading Cache**: Clear the internal SD card cache. - **Check for updates**: Check for CrossInk firmware updates over WiFi. - **Language**: Set the system language (see **[Supported Languages](#supported-languages)** for more information). diff --git a/docs/catalog b/docs/catalog index faaa50a9..3e63feab 100644 --- a/docs/catalog +++ b/docs/catalog @@ -2,48 +2,48 @@ "schema_version": 1, "releases": [ { - "id": "stable-1.2.9.1-tiny", + "id": "stable-1.2.11-tiny", "channel": "stable", - "name": "1.2.9.1", - "version": "1.2.9.1", + "name": "1.2.11", + "version": "1.2.11", "variant": "tiny", - "released_at": "2026-05-06T00:17:55Z", - "notes": "CrossInk 1.2.9.1 stable firmware", - "firmware_url": "https://github.com/uxjulia/CrossInk/releases/latest/download/firmware-tiny-v1.2.9.1.bin", - "firmware_sha256": "0ca2cf87bf3b2915778d588a059698574bd896b41a41a08b91daa283b34adc1f", - "size": 6290704, + "released_at": "2026-05-15T02:32:34Z", + "notes": "CrossInk 1.2.11 stable firmware", + "firmware_url": "https://github.com/uxjulia/CrossInk/releases/latest/download/firmware-tiny-v1.2.11.bin", + "firmware_sha256": "83fea31801331bb3e694b480a06eef46774508360bdc4fbc96b127f555ada41d", + "size": 6485680, "supported_devices": [ "x4", "x3" ] }, { - "id": "stable-1.2.9.1-xlarge", + "id": "stable-1.2.11-xlarge", "channel": "stable", - "name": "1.2.9.1", - "version": "1.2.9.1", + "name": "1.2.11", + "version": "1.2.11", "variant": "xlarge", - "released_at": "2026-05-06T00:17:55Z", - "notes": "CrossInk 1.2.9.1 stable firmware", - "firmware_url": "https://github.com/uxjulia/CrossInk/releases/latest/download/firmware-xlarge-v1.2.9.1.bin", - "firmware_sha256": "3f422e565d2acf2ad1765dfc4954f435c17e33146ef64298155d18010d5660de", - "size": 6365376, + "released_at": "2026-05-15T02:32:34Z", + "notes": "CrossInk 1.2.11 stable firmware", + "firmware_url": "https://github.com/uxjulia/CrossInk/releases/latest/download/firmware-xlarge-v1.2.11.bin", + "firmware_sha256": "c3937885d9199db354df2170ce09ec4118c2c5429e8d821fff1e2756ce5f3d17", + "size": 6560240, "supported_devices": [ "x4", "x3" ] }, { - "id": "stable-1.2.9.1-no_emoji", + "id": "stable-1.2.11-no_emoji", "channel": "stable", - "name": "1.2.9.1", - "version": "1.2.9.1", + "name": "1.2.11", + "version": "1.2.11", "variant": "no_emoji", - "released_at": "2026-05-06T00:17:55Z", - "notes": "CrossInk 1.2.9.1 stable firmware", - "firmware_url": "https://github.com/uxjulia/CrossInk/releases/latest/download/firmware-no_emoji-v1.2.9.1.bin", - "firmware_sha256": "0b076b925c0378231c9a15f007eb60dc83cb41efd1aa241ba7d452789623b136", - "size": 6471904, + "released_at": "2026-05-15T02:32:34Z", + "notes": "CrossInk 1.2.11 stable firmware", + "firmware_url": "https://github.com/uxjulia/CrossInk/releases/latest/download/firmware-no_emoji-v1.2.11.bin", + "firmware_sha256": "349711f9f189d9fe9aba3f3e62527cc5cd2ca68ae646766ac33dca4b7c45c88b", + "size": 6213904, "supported_devices": [ "x4", "x3" diff --git a/docs/file-formats.md b/docs/file-formats.md index a408043b..9a8a4a51 100644 --- a/docs/file-formats.md +++ b/docs/file-formats.md @@ -7,7 +7,9 @@ nav_order: 8 ## `book.bin` -### Version 3 +### Version 6 + +Adds a Crossink-owned cache magic before the version byte so `book.bin` files written by upstream CrossPoint or other forks are rejected and rebuilt instead of being parsed as compatible Crossink metadata caches. ImHex Pattern: @@ -17,7 +19,8 @@ import std.string; import std.core; // === Configuration === -#define EXPECTED_VERSION 3 +#define EXPECTED_MAGIC 0x425843FF +#define EXPECTED_VERSION 6 #define MAX_STRING_LENGTH 65535 // === String Structure === @@ -39,6 +42,7 @@ fn format_string(String s) { struct Metadata { String title [[comment("Book title")]]; String author [[comment("Book author")]]; + String language [[comment("Book language")]]; String coverItemHref [[comment("Path to cover image")]]; String textReferenceHref [[comment("Path to guided first text reference")]]; } [[comment("Book metadata information")]]; @@ -65,6 +69,11 @@ struct TocEntry { struct BookBin { // Header + u32 magic [[comment("Crossink cache magic: 0xFF CXB"), color("B8F7D4")]]; + if (magic != EXPECTED_MAGIC) { + std::error(std::format("Unsupported cache magic: 0x{:08X} (expected 0x{:08X})", magic, EXPECTED_MAGIC)); + } + u8 version [[comment("Format version"), color("FFD93D")]]; // Version validation @@ -109,6 +118,34 @@ if (parsedSize != fileSize) { ## `section.bin` +### Version 36 + +Adds a per-`TextBlock` Guide Dots metadata flag before the guide-dot offset vector. When no word in the block has a guide dot, the guide-dot offset vector is omitted; background bytes remain per-word. + +### Version 35 + +Adds a per-`TextBlock` Bionic Reading metadata flag before the bionic boundary/suffix vectors. When no word in the block has a bionic split, the boundary and suffix vectors are omitted; guide-dot offsets and background bytes remain per-word. + +### Version 34 + +Adds a Crossink-owned cache magic before the version byte so `sections/*.bin` files written by upstream CrossPoint or other forks are rejected and rebuilt instead of being parsed as compatible Crossink section caches. + +### Version 33 + +Adds one per-word `backgroundBlack` byte to serialized `TextBlock` records so EPUB text runs with simple black CSS backgrounds can repaint correctly from section cache. + +### Version 32 + +Invalidates cached EPUB section files after image rendering and image-cache behavior changes, so old rendered page data is rebuilt instead of reusing stale `ImageBlock` cache references. No binary layout fields changed from version 31. + +### Version 31 + +Invalidates cached EPUB section files after section-cache write hardening and parser/layout compatibility changes from the upstream merge. No binary layout fields changed from version 30. + +### Version 30 + +Added `PageHorizontalRule` page elements (tag `4`) for rendered EPUB `
    ` separators. Horizontal rules store their x/y position, width, and thickness. This invalidates cached section layouts so horizontal rules are serialized as first-class page elements instead of being ignored. + ### Version 29 Added `PageTableFragment` page elements (tag `3`) for paginated simple-table layout. Table fragments store their fragment width, column count, cell padding, line height, per-row heights/header-divider flags, and per-cell serialized `TextBlock` lines. This invalidates cached section layouts so simple EPUB tables can render as buffered multi-column grids instead of flattened cell paragraphs. @@ -131,7 +168,7 @@ Bionic Reading words are now stored as a single merged TextBlock entry instead o ### Version 23 -Added `guideReadingEnabled` (bool) to the header after `focusReadingEnabled`. Guide Dots feature flag: when enabled, a middle dot (U+00B7) is inserted between words during layout (skipped for Justify alignment). +Added `guideReadingEnabled` (bool) to the header after `bionicReadingEnabled`. Guide Dots feature flag: when enabled, a middle dot (U+00B7) is inserted between words during layout (skipped for Justify alignment). ### Version 22 @@ -145,7 +182,8 @@ import std.string; import std.core; // === Configuration === -#define EXPECTED_VERSION 29 +#define EXPECTED_MAGIC 0x535843FF +#define EXPECTED_VERSION 36 #define MAX_STRING_LENGTH 65535 #define MAX_WORD_STRING_LENGTH 4096 #define FOOTNOTE_NUMBER_LEN 32 @@ -182,7 +220,8 @@ fn format_word_string(WordString s) { enum PageElementTag : u8 { PageLine = 1, PageImage = 2, - PageTableFragment = 3 + PageTableFragment = 3, + PageHorizontalRule = 4 }; enum WordStyle : u8 { @@ -206,9 +245,16 @@ struct TextBlock { WordString words[wordCount]; s16 wordXPos[wordCount]; WordStyle wordStyle[wordCount]; - u8 wordBionicBoundary[wordCount]; - u16 wordBionicSuffixX[wordCount]; - u16 wordGuideDotXOffset[wordCount]; + u8 hasBionicMetadata; + if (hasBionicMetadata != 0) { + u8 wordBionicBoundary[wordCount]; + u16 wordBionicSuffixX[wordCount]; + } + u8 hasGuideDotMetadata; + if (hasGuideDotMetadata != 0) { + u16 wordGuideDotXOffset[wordCount]; + } + u8 wordBackgroundBlack[wordCount]; BlockStyle blockStyle; bool textAlignDefined; s16 marginTop; @@ -229,6 +275,13 @@ struct PageLine { TextBlock textBlock [[inline]]; }; +struct PageHorizontalRule { + s16 xPos; + s16 yPos; + u16 width; + u8 thickness; +}; + struct ImageBlock { String imagePath [[comment("Original cached image path")]]; s16 width; @@ -278,6 +331,8 @@ struct PageElement { PageImage pageImage [[inline]]; } else if (pageElementType == 3) { PageTableFragment pageTableFragment [[inline]]; + } else if (pageElementType == 4) { + PageHorizontalRule pageHorizontalRule [[inline]]; } else { std::error(std::format("Unknown page element type: {}", pageElementType)); } @@ -299,6 +354,11 @@ struct AnchorEntry { struct SectionBin { // Header + u32 magic [[comment("Crossink cache magic: 0xFF CXS"), color("B8F7D4")]]; + if (magic != EXPECTED_MAGIC) { + std::error(std::format("Unsupported cache magic: 0x{:08X} (expected 0x{:08X})", magic, EXPECTED_MAGIC)); + } + u8 version [[comment("Format version"), color("FFD93D")]]; // Version validation @@ -323,6 +383,7 @@ struct SectionBin { u32 lutOffset; u32 anchorMapOffset; u32 paragraphLutOffset; + u32 liLutOffset; Page pages[pageCount] [[inline]]; @@ -350,6 +411,13 @@ struct SectionBin { } u16 paragraphCount; u16 paragraphIndices[paragraphCount]; + + // List-item index -> page lookup + u32 liOffsetCheck = $; + if (liOffsetCheck != liLutOffset) { + std::warning(std::format("List-item LUT offset mismatch: expected 0x{:X}, got 0x{:X}", liLutOffset, liOffsetCheck)); + } + u16 listItemIndices[paragraphCount]; }; // === File Parsing === @@ -364,3 +432,9 @@ if (parsedSize != fileSize) { std::warning(std::format("Unparsed data detected: {} bytes remaining at offset 0x{:X}", fileSize - parsedSize, parsedSize)); } ``` + +## `css_rules.cache` + +### Version 8 + +Adds a Crossink-owned cache magic before the version byte so cached EPUB CSS rules written by upstream CrossPoint or other forks are rejected and rebuilt before section caches are regenerated. diff --git a/docs/focus-reading.md b/docs/focus-reading.md new file mode 100644 index 00000000..992c9e62 --- /dev/null +++ b/docs/focus-reading.md @@ -0,0 +1,33 @@ +# Bionic Reading + +Bionic Reading is a reading aid that bolds the first portion of each word, guiding your eyes to natural fixation points and helping you read faster with less effort. Some readers - particularly those with ADHD - find it helps them stay engaged with the text and reduces mind-wandering. + +Comparison of the same page with and without Bionic Reading enabled + +*Left: Bionic Reading off. Right: Bionic Reading on. Both using Literata.* + +## Enabling Bionic Reading + +1. Open **Settings > Reader** +2. Toggle **Bionic Reading** on + +Toggling the setting will trigger a re-index of your current book, the same as when changing font settings. Once indexing is complete, page turns proceed as normal. No changes are made to your EPUB files. + +## Examples + +Bionic Reading with Noto Serif font + +*Bionic Reading with Noto Serif font* + +Bionic Reading with Merriweather font + +*Bionic Reading with Merriweather font* + +Bionic Reading with Atkinson Hyperlegible Next font + +*Bionic Reading with Atkinson Hyperlegible Next font* + +## Notes + +- Bionic Reading only applies to regular body text. Already-bold text (headings, emphasis) is left unchanged. +- The setting is per-device, not per-book — it applies to all books while enabled. diff --git a/docs/hyphenation-trie-format.md b/docs/hyphenation-trie-format.md index efe4af8d..2fa24e68 100644 --- a/docs/hyphenation-trie-format.md +++ b/docs/hyphenation-trie-format.md @@ -54,5 +54,5 @@ A convenient script `update_hyphenation.sh` is used to update all languages. To use it, run: ```sh -./scripts/update_hypenation.sh +./scripts/update_hyphenation.sh ``` diff --git a/docs/images/focus-reading/focus-reading-atkinson.jpg b/docs/images/focus-reading/focus-reading-atkinson.jpg new file mode 100644 index 00000000..8ec77b10 Binary files /dev/null and b/docs/images/focus-reading/focus-reading-atkinson.jpg differ diff --git a/docs/images/focus-reading/focus-reading-merriweather.jpg b/docs/images/focus-reading/focus-reading-merriweather.jpg new file mode 100644 index 00000000..59d82371 Binary files /dev/null and b/docs/images/focus-reading/focus-reading-merriweather.jpg differ diff --git a/docs/images/focus-reading/focus-reading-notoserif.jpg b/docs/images/focus-reading/focus-reading-notoserif.jpg new file mode 100644 index 00000000..8ffc869d Binary files /dev/null and b/docs/images/focus-reading/focus-reading-notoserif.jpg differ diff --git a/docs/images/focus-reading/focus-reading.jpg b/docs/images/focus-reading/focus-reading.jpg new file mode 100644 index 00000000..97318e7b Binary files /dev/null and b/docs/images/focus-reading/focus-reading.jpg differ diff --git a/docs/sd-card-fonts.md b/docs/sd-card-fonts.md new file mode 100644 index 00000000..db9b1277 --- /dev/null +++ b/docs/sd-card-fonts.md @@ -0,0 +1,104 @@ +# SD Card Fonts + +CrossPoint supports loading additional fonts from the SD card, including fonts +with extended Unicode coverage (CJK, Cyrillic, Greek, etc.). + +## Installing Fonts + +There are three ways to install fonts: + +### Option 1: Download from device (recommended) + +1. Connect your CrossPoint reader to WiFi +2. Go to **Settings > System > Manage Fonts** +3. Browse available font families and tap to download +4. Downloaded fonts appear immediately in **Settings > Reader > Font Family** + +### Option 2: Upload via web browser + +1. Connect your CrossPoint reader to WiFi +2. Open the web interface in your browser (shown on the WiFi screen) +3. Navigate to the **Fonts** tab +4. Upload `.cpfont` files using the upload form + +### Option 3: Manual SD card copy + +1. Download font files from the + [crosspoint-fonts repository](https://github.com/crosspoint-reader/crosspoint-fonts) +2. Copy font family folders to one of two locations on your SD card: + + - `/.fonts/` — hidden directory (preferred; keeps the SD root tidy + when mounted on a desktop) + - `/fonts/` — visible directory (use this if your OS hides dot-files + and you'd rather see the folder in your file manager) + + Both roots are always scanned at boot and the results are merged: a + family installed in `/fonts/` shows up even when `/.fonts/` also + exists, and vice versa. The two roots only collide if the same family + name appears in both — in that case the copy in `/.fonts/` wins and + the duplicate in `/fonts/` is ignored. + + SD Card Root/ + ├── .fonts/ ← Hidden root (preferred) + │ └── Literata/ + │ ├── Literata_12.cpfont + │ ├── Literata_14.cpfont + │ ├── Literata_16.cpfont + │ └── Literata_18.cpfont + └── fonts/ ← Visible root (equally valid) + └── Merriweather/ + ├── Merriweather_12.cpfont + └── ... + +3. Insert the SD card and power on your CrossPoint reader + +## Available Pre-Built Fonts + +The current list of pre-built fonts is maintained in the +[crosspoint-fonts repository](https://github.com/crosspoint-reader/crosspoint-fonts). + +## Converting Custom Fonts + +To convert your own TrueType/OpenType fonts: + +### Prerequisites + + pip install freetype-py fonttools + +### Single font (one style) + + python3 lib/EpdFont/scripts/fontconvert_sdcard.py \ + MyFont-Regular.ttf \ + --intervals latin-ext \ + --sizes 12,14,16,18 \ + --style regular \ + --name MyFont \ + --output-dir ./MyFont/ + +### Multi-style font + + python3 lib/EpdFont/scripts/fontconvert_sdcard.py \ + --regular MyFont-Regular.ttf \ + --bold MyFont-Bold.ttf \ + --italic MyFont-Italic.ttf \ + --bolditalic MyFont-BoldItalic.ttf \ + --intervals latin-ext \ + --sizes 12,14,16,18 \ + --name MyFont \ + --output-dir ./MyFont/ + +### Available Unicode interval presets + +| Preset | Coverage | +|--------|----------| +| `ascii` | U+0020-U+007E (Basic Latin) | +| `latin-ext` | European languages (Latin + Extended-A/B) | +| `greek` | Greek + Extended Greek | +| `cyrillic` | Cyrillic + Supplement | +| `cjk` | CJK Unified Ideographs + Hiragana + Katakana + Fullwidth | +| `hangul` | Korean Hangul syllables | +| `builtin` | Matches built-in Bookerly coverage exactly | + +Combine presets with commas: `--intervals latin-ext,greek,cyrillic` + +Install custom fonts via WiFi upload or manual SD card copy. diff --git a/docs/translators.md b/docs/translators.md index b945476e..90d87ebe 100644 --- a/docs/translators.md +++ b/docs/translators.md @@ -58,6 +58,7 @@ If you'd like to add your name to this list, please open a PR adding yourself an ## Ukrainian - [mirus-ua](https://github.com/mirus-ua) +- [KymAndriy](https://github.com/KymAndriy) ## Belarusian - [Dexif](https://github.com/dexif) diff --git a/lib/EpdFont/EpdFont.cpp b/lib/EpdFont/EpdFont.cpp index c3aafd52..3a418213 100644 --- a/lib/EpdFont/EpdFont.cpp +++ b/lib/EpdFont/EpdFont.cpp @@ -153,21 +153,23 @@ uint32_t EpdFont::applyLigatures(uint32_t cp, const char*& text) const { const EpdGlyph* EpdFont::findGlyph(const uint32_t cp) const { const int count = data->intervalCount; - if (count == 0) return nullptr; - - const EpdUnicodeInterval* intervals = data->intervals; - const auto* end = intervals + count; - - // upper_bound: range lookup. Finds the first interval with first > cp, so the - // interval just before it is the last one with first <= cp. That's the only - // candidate that could contain cp. Then we verify cp <= candidate.last. - const auto it = std::upper_bound( - intervals, end, cp, [](uint32_t value, const EpdUnicodeInterval& interval) { return value < interval.first; }); - - if (it != intervals) { - const auto& interval = *(it - 1); - if (cp <= interval.last) { - return &data->glyph[interval.offset + (cp - interval.first)]; + if (count == 0 && !data->glyphMissHandler) return nullptr; + + if (count > 0) { + const EpdUnicodeInterval* intervals = data->intervals; + const auto* end = intervals + count; + + // upper_bound: range lookup. Finds the first interval with first > cp, so the + // interval just before it is the last one with first <= cp. That's the only + // candidate that could contain cp. Then we verify cp <= candidate.last. + const auto it = std::upper_bound( + intervals, end, cp, [](uint32_t value, const EpdUnicodeInterval& interval) { return value < interval.first; }); + + if (it != intervals) { + const auto& interval = *(it - 1); + if (cp <= interval.last) { + return &data->glyph[interval.offset + (cp - interval.first)]; + } } } @@ -179,6 +181,12 @@ const EpdGlyph* EpdFont::getGlyph(const uint32_t cp) const { return glyph; } + // Codepoint not in interval table — try on-demand loading (SD card fonts). + if (data->glyphMissHandler) { + const EpdGlyph* loaded = data->glyphMissHandler(data->glyphMissCtx, cp); + if (loaded) return loaded; + } + if (cp != REPLACEMENT_GLYPH) { return getGlyph(REPLACEMENT_GLYPH); } diff --git a/lib/EpdFont/EpdFontData.h b/lib/EpdFont/EpdFontData.h index 380c5733..d01c0281 100644 --- a/lib/EpdFont/EpdFontData.h +++ b/lib/EpdFont/EpdFontData.h @@ -129,4 +129,148 @@ typedef struct { uint8_t kernRightClassCount; ///< Number of distinct right classes (matrix cols) const EpdLigaturePair* ligaturePairs; ///< Sorted ligature pair table (nullptr if none) uint32_t ligaturePairCount; ///< Number of entries in ligaturePairs + + /// On-demand glyph loading for fonts that don't keep all glyphs in RAM (e.g. SD card fonts). + /// Called by getGlyph() when a codepoint is not found in the interval table. + /// Returns a valid EpdGlyph* with correct metadata, or nullptr to fall back to the + /// replacement glyph. The returned pointer is valid until the next glyphMissHandler + /// call that causes a ring-buffer eviction — callers must consume it (measure or draw) + /// before requesting another missed glyph. + const EpdGlyph* (*glyphMissHandler)(void* ctx, uint32_t codepoint); + + /// Context pointer for glyphMissHandler (typically SdCardFont*). Also used by + /// GfxRenderer::getGlyphBitmap() to retrieve overflow bitmaps via SdCardFont. + void* glyphMissCtx; } EpdFontData; + +namespace syntheticGlyph { + +constexpr uint32_t FULL_BLOCK = 0x2588; +constexpr uint32_t BLACK_SQUARE = 0x25A0; +constexpr uint32_t GREEK_CAPITAL_GAMMA = 0x0393; +constexpr uint32_t GREEK_SMALL_EPSILON = 0x03B5; +constexpr uint32_t GREEK_SMALL_OMEGA = 0x03C9; +constexpr uint32_t MODIFIER_LETTER_TURNED_COMMA = 0x02BB; +constexpr uint32_t LEFT_SINGLE_QUOTATION_MARK = 0x2018; + +constexpr bool isSolid(uint32_t cp) { return cp == FULL_BLOCK || cp == BLACK_SQUARE; } +constexpr bool isGreekFallback(uint32_t cp) { + return cp == GREEK_CAPITAL_GAMMA || cp == GREEK_SMALL_EPSILON || cp == GREEK_SMALL_OMEGA; +} +constexpr bool isReplacementFallback(uint32_t cp) { return cp == 0xFFFD; } +constexpr bool isSpaceFallback(uint32_t cp) { + return cp == 0x00A0 || cp == 0x1680 || (cp >= 0x2000 && cp <= 0x200A) || cp == 0x202F || cp == 0x205F || cp == 0x3000; +} +constexpr uint32_t aliasCodepoint(uint32_t cp) { + return cp == MODIFIER_LETTER_TURNED_COMMA ? LEFT_SINGLE_QUOTATION_MARK : cp; +} + +inline uint16_t solidAdvanceX(const EpdFontData* data, const EpdGlyph* emGlyph) { + if (emGlyph && emGlyph->advanceX > 0) { + return emGlyph->advanceX; + } + int px = data && data->ascender > 0 ? (data->ascender * 3 + 3) / 4 : 8; + if (px < 1) px = 1; + return static_cast(fp4::fromPixel(px)); +} + +inline int solidHeight(const EpdFontData* data, uint32_t cp) { + int ascender = data && data->ascender > 0 ? data->ascender : 8; + if (cp == BLACK_SQUARE) { + int height = (ascender * 2 + 2) / 3; + return height > 1 ? height : 1; + } + return ascender > 1 ? ascender : 1; +} + +inline int solidWidth(uint32_t cp, uint16_t advanceX, int height) { + const int advancePx = fp4::toPixel(advanceX); + if (cp == BLACK_SQUARE) { + return height < advancePx ? height : advancePx; + } + return advancePx > 1 ? advancePx : 1; +} + +inline int solidLeft(uint32_t cp, uint16_t advanceX, int width) { + if (cp != BLACK_SQUARE) return 0; + const int advancePx = fp4::toPixel(advanceX); + return advancePx > width ? (advancePx - width) / 2 : 0; +} + +inline int solidTop(const EpdFontData* data, uint32_t cp, int height) { + const int ascender = data && data->ascender > 0 ? data->ascender : height; + if (cp != BLACK_SQUARE || ascender <= height) return height; + return height + (ascender - height) / 2; +} + +inline uint16_t replacementAdvanceX(const EpdFontData* data, const EpdGlyph* emGlyph) { + if (emGlyph && emGlyph->advanceX > 0) { + return emGlyph->advanceX; + } + int px = data && data->ascender > 0 ? (data->ascender * 3 + 3) / 4 : 8; + if (px < 3) px = 3; + return static_cast(fp4::fromPixel(px)); +} + +inline int replacementHeight(const EpdFontData* data) { + const int ascender = data && data->ascender > 0 ? data->ascender : 8; + return ascender > 3 ? ascender : 3; +} + +inline int replacementWidth(uint16_t advanceX, int height) { + const int advancePx = fp4::toPixel(advanceX); + int width = advancePx < height ? advancePx : height; + if (width < 3) width = 3; + return width; +} + +inline int replacementLeft(uint16_t advanceX, int width) { + const int advancePx = fp4::toPixel(advanceX); + return advancePx > width ? (advancePx - width) / 2 : 0; +} + +inline int replacementTop(const EpdFontData* data, int height) { + const int ascender = data && data->ascender > 0 ? data->ascender : height; + return ascender > height ? height + (ascender - height) / 2 : height; +} + +inline uint16_t greekAdvanceX(const EpdFontData* data, const EpdGlyph* emGlyph, uint32_t cp) { + if (cp == GREEK_SMALL_OMEGA && emGlyph && emGlyph->advanceX > 0) { + return emGlyph->advanceX; + } + const int ascender = data && data->ascender > 0 ? data->ascender : 8; + int px = cp == GREEK_CAPITAL_GAMMA ? (ascender * 7 + 5) / 10 : (ascender * 3 + 3) / 4; + if (px < 1) px = 1; + return static_cast(fp4::fromPixel(px)); +} + +inline int greekHeight(const EpdFontData* data, uint32_t cp) { + int ascender = data && data->ascender > 0 ? data->ascender : 8; + if (cp != GREEK_CAPITAL_GAMMA) { + ascender = (ascender * 3 + 3) / 4; + } + return ascender > 1 ? ascender : 1; +} + +inline int greekWidth(uint32_t cp, uint16_t advanceX, int height) { + const int advancePx = fp4::toPixel(advanceX); + if (cp == GREEK_CAPITAL_GAMMA) { + const int width = height > 2 ? (height * 6 + 5) / 10 : height; + return width < advancePx ? width : advancePx; + } + return advancePx > 1 ? advancePx : 1; +} + +inline int greekLeft(uint32_t cp, uint16_t advanceX, int width) { + const int advancePx = fp4::toPixel(advanceX); + if (cp == GREEK_CAPITAL_GAMMA) return 0; + return advancePx > width ? (advancePx - width) / 2 : 0; +} + +inline int greekTop(const EpdFontData* data, uint32_t cp, int height) { + const int ascender = data && data->ascender > 0 ? data->ascender : height; + if (cp == GREEK_CAPITAL_GAMMA || ascender <= height) return height; + return height + (ascender - height) / 2; +} + +} // namespace syntheticGlyph diff --git a/lib/EpdFont/EpdFontFamily.cpp b/lib/EpdFont/EpdFontFamily.cpp index f91607cc..51a9f170 100644 --- a/lib/EpdFont/EpdFontFamily.cpp +++ b/lib/EpdFont/EpdFontFamily.cpp @@ -43,6 +43,97 @@ void EpdFontFamily::getTextDimensions(const char* string, int* w, int* h, const if (!isCombining) { cp = applyLigatures(cp, string, style); + cp = getFallbackCodepoint(cp, style); + } + + const bool hasRealGlyph = findGlyphData(cp, style).glyph != nullptr; + + if (!isCombining && !hasRealGlyph && syntheticGlyph::isSpaceFallback(cp)) { + lastBaseX += fp4::toPixel(prevAdvanceFP); + prevCp = 0; + prevAdvanceFP = 0; + lastBaseLeft = 0; + lastBaseWidth = 0; + lastBaseTop = 0; + continue; + } + + if (!isCombining && !hasRealGlyph && syntheticGlyph::isSolid(cp)) { + const EpdFontData* data = getData(style); + const uint16_t advanceX = syntheticGlyph::solidAdvanceX(data, getGlyph('M', style)); + const int glyphHeight = syntheticGlyph::solidHeight(data, cp); + const int glyphWidth = syntheticGlyph::solidWidth(cp, advanceX, glyphHeight); + const int glyphLeft = syntheticGlyph::solidLeft(cp, advanceX, glyphWidth); + const int glyphTop = syntheticGlyph::solidTop(data, cp, glyphHeight); + + if (prevCp != 0) { + const auto kernFP = getKerning(prevCp, cp, style); + lastBaseX += fp4::toPixel(prevAdvanceFP + kernFP); + } + + minX = std::min(minX, lastBaseX + glyphLeft); + maxX = std::max(maxX, lastBaseX + glyphLeft + glyphWidth); + minY = std::min(minY, glyphTop - glyphHeight); + maxY = std::max(maxY, glyphTop); + + lastBaseLeft = glyphLeft; + lastBaseWidth = glyphWidth; + lastBaseTop = glyphTop; + prevAdvanceFP = advanceX; + prevCp = cp; + continue; + } + + if (!isCombining && !hasRealGlyph && syntheticGlyph::isGreekFallback(cp)) { + const EpdFontData* data = getData(style); + const uint16_t advanceX = syntheticGlyph::greekAdvanceX(data, getGlyph('M', style), cp); + const int glyphHeight = syntheticGlyph::greekHeight(data, cp); + const int glyphWidth = syntheticGlyph::greekWidth(cp, advanceX, glyphHeight); + const int glyphLeft = syntheticGlyph::greekLeft(cp, advanceX, glyphWidth); + const int glyphTop = syntheticGlyph::greekTop(data, cp, glyphHeight); + + if (prevCp != 0) { + const auto kernFP = getKerning(prevCp, cp, style); + lastBaseX += fp4::toPixel(prevAdvanceFP + kernFP); + } + + minX = std::min(minX, lastBaseX + glyphLeft); + maxX = std::max(maxX, lastBaseX + glyphLeft + glyphWidth); + minY = std::min(minY, glyphTop - glyphHeight); + maxY = std::max(maxY, glyphTop); + + lastBaseLeft = glyphLeft; + lastBaseWidth = glyphWidth; + lastBaseTop = glyphTop; + prevAdvanceFP = advanceX; + prevCp = cp; + continue; + } + + if (!isCombining && !hasRealGlyph && syntheticGlyph::isReplacementFallback(cp)) { + const EpdFontData* data = getData(style); + const uint16_t advanceX = syntheticGlyph::replacementAdvanceX(data, getGlyph('M', style)); + const int glyphHeight = syntheticGlyph::replacementHeight(data); + const int glyphWidth = syntheticGlyph::replacementWidth(advanceX, glyphHeight); + const int glyphLeft = syntheticGlyph::replacementLeft(advanceX, glyphWidth); + const int glyphTop = syntheticGlyph::replacementTop(data, glyphHeight); + + if (prevCp != 0) { + const auto kernFP = getKerning(prevCp, cp, style); + lastBaseX += fp4::toPixel(prevAdvanceFP + kernFP); + } + + minX = std::min(minX, lastBaseX + glyphLeft); + maxX = std::max(maxX, lastBaseX + glyphLeft + glyphWidth); + minY = std::min(minY, glyphTop - glyphHeight); + maxY = std::max(maxY, glyphTop); + + lastBaseLeft = glyphLeft; + lastBaseWidth = glyphWidth; + lastBaseTop = glyphTop; + prevAdvanceFP = advanceX; + prevCp = cp; + continue; } const EpdGlyph* glyph = getGlyph(cp, style); @@ -90,7 +181,7 @@ void EpdFontFamily::getTextDimensions(const char* string, int* w, int* h, const const EpdFontData* EpdFontFamily::getData(const Style style) const { return getFont(style)->data; } -EpdFontFamily::GlyphData EpdFontFamily::getGlyphData(const uint32_t cp, const Style style) const { +EpdFontFamily::GlyphData EpdFontFamily::findGlyphData(const uint32_t cp, const Style style) const { const EpdFont* font = getFont(style); if (const EpdGlyph* glyph = font->findGlyph(cp)) { return {font->data, glyph}; @@ -102,6 +193,14 @@ EpdFontFamily::GlyphData EpdFontFamily::getGlyphData(const uint32_t cp, const St } } + return {nullptr, nullptr}; +} + +EpdFontFamily::GlyphData EpdFontFamily::getGlyphData(const uint32_t cp, const Style style) const { + if (const GlyphData glyphData = findGlyphData(cp, style); glyphData.glyph) { + return glyphData; + } + if (cp != REPLACEMENT_GLYPH) { return getGlyphData(REPLACEMENT_GLYPH, style); } @@ -112,6 +211,17 @@ const EpdGlyph* EpdFontFamily::getGlyph(const uint32_t cp, const Style style) co return getGlyphData(cp, style).glyph; } +uint32_t EpdFontFamily::getFallbackCodepoint(const uint32_t cp, const Style style) const { + if (findGlyphData(cp, style).glyph) return cp; + const uint32_t aliasCp = syntheticGlyph::aliasCodepoint(cp); + if (aliasCp != cp) { + return findGlyphData(aliasCp, style).glyph ? aliasCp : REPLACEMENT_GLYPH; + } + if (syntheticGlyph::isSpaceFallback(cp)) return cp; + if (syntheticGlyph::isSolid(cp) || syntheticGlyph::isGreekFallback(cp)) return cp; + return REPLACEMENT_GLYPH; +} + int8_t EpdFontFamily::getKerning(const uint32_t leftCp, const uint32_t rightCp, const Style style) const { return getFont(style)->getKerning(leftCp, rightCp); } diff --git a/lib/EpdFont/EpdFontFamily.h b/lib/EpdFont/EpdFontFamily.h index d3d575f0..e4fa51e5 100644 --- a/lib/EpdFont/EpdFontFamily.h +++ b/lib/EpdFont/EpdFontFamily.h @@ -15,8 +15,10 @@ class EpdFontFamily { ~EpdFontFamily() = default; void getTextDimensions(const char* string, int* w, int* h, Style style = REGULAR) const; const EpdFontData* getData(Style style = REGULAR) const; + GlyphData findGlyphData(uint32_t cp, Style style = REGULAR) const; GlyphData getGlyphData(uint32_t cp, Style style = REGULAR) const; const EpdGlyph* getGlyph(uint32_t cp, Style style = REGULAR) const; + uint32_t getFallbackCodepoint(uint32_t cp, Style style = REGULAR) const; int8_t getKerning(uint32_t leftCp, uint32_t rightCp, Style style = REGULAR) const; uint32_t applyLigatures(uint32_t cp, const char*& text, Style style = REGULAR) const; diff --git a/lib/EpdFont/FontDecompressor.cpp b/lib/EpdFont/FontDecompressor.cpp index 181bf8d0..b4a940f6 100644 --- a/lib/EpdFont/FontDecompressor.cpp +++ b/lib/EpdFont/FontDecompressor.cpp @@ -24,12 +24,12 @@ void FontDecompressor::clearCache() { } void FontDecompressor::freePageBuffer() { - free(pageBuffer); - pageBuffer = nullptr; - free(pageGlyphs); - pageGlyphs = nullptr; - pageFont = nullptr; - pageGlyphCount = 0; + for (uint8_t i = 0; i < pageSlotCount; i++) { + free(pageSlots[i].buffer); + free(pageSlots[i].glyphs); + pageSlots[i] = {}; + } + pageSlotCount = 0; } void FontDecompressor::freeHotGroup() { @@ -126,7 +126,7 @@ void FontDecompressor::compactSingleGlyph(const uint8_t* alignedSrc, uint8_t* pa if (outBits > 0) packedDst[writeIdx] = outByte << (8 - outBits); } -// --- getBitmap: page buffer → hot group → decompress --- +// --- getBitmap: page slots → hot group → decompress --- const uint8_t* FontDecompressor::getBitmap(const EpdFontData* fontData, const EpdGlyph* glyph, uint32_t glyphIndex) { const uint32_t tStart = micros(); @@ -137,24 +137,28 @@ const uint8_t* FontDecompressor::getBitmap(const EpdFontData* fontData, const Ep return &fontData->bitmap[glyph->dataOffset]; } - // Check page buffer first (populated by prewarmCache) - if (pageBuffer && pageFont == fontData && pageGlyphCount > 0) { - int left = 0, right = pageGlyphCount - 1; + // Check page buffers first (populated by prewarmCache) + for (uint8_t slotIndex = 0; slotIndex < pageSlotCount; slotIndex++) { + PageSlot& slot = pageSlots[slotIndex]; + if (!slot.buffer || !slot.glyphs || slot.fontData != fontData || slot.glyphCount == 0) continue; + + int left = 0, right = slot.glyphCount - 1; while (left <= right) { int mid = left + (right - left) / 2; - if (pageGlyphs[mid].glyphIndex == glyphIndex) { - if (pageGlyphs[mid].bufferOffset != UINT32_MAX) { + if (slot.glyphs[mid].glyphIndex == glyphIndex) { + if (slot.glyphs[mid].bufferOffset != UINT32_MAX) { stats.cacheHits++; stats.getBitmapTimeUs += micros() - tStart; - return &pageBuffer[pageGlyphs[mid].bufferOffset]; + return &slot.buffer[slot.glyphs[mid].bufferOffset]; } break; // Not extracted during prewarm; fall through to hot-group path } - if (pageGlyphs[mid].glyphIndex < glyphIndex) + if (slot.glyphs[mid].glyphIndex < glyphIndex) left = mid + 1; else right = mid - 1; } + break; } // Fallback: hot group slot @@ -239,8 +243,11 @@ int32_t FontDecompressor::findGlyphIndex(const EpdFontData* fontData, uint32_t c } int FontDecompressor::prewarmCache(const EpdFontData* fontData, const char* utf8Text) { - freePageBuffer(); if (!fontData || !fontData->groups || !utf8Text) return 0; + if (pageSlotCount >= MAX_PAGE_SLOTS) { + LOG_DBG("FDC", "Prewarm slot cap (%u) reached; using hot-group fallback", MAX_PAGE_SLOTS); + return -1; + } // Step 1: Collect unique glyph indices needed for this page uint32_t neededGlyphs[MAX_PAGE_GLYPHS]; @@ -253,6 +260,9 @@ int FontDecompressor::prewarmCache(const EpdFontData* fontData, const char* utf8 if (cp == 0) break; int32_t glyphIdx = findGlyphIndex(fontData, cp); + if (glyphIdx < 0 && !syntheticGlyph::isSpaceFallback(cp)) { + glyphIdx = findGlyphIndex(fontData, REPLACEMENT_GLYPH); + } if (glyphIdx < 0) continue; // Deduplicate @@ -274,6 +284,43 @@ int FontDecompressor::prewarmCache(const EpdFontData* fontData, const char* utf8 } } + // Add ligature output glyphs: if both input codepoints of a ligature pair are + // in the needed set, the output glyph will be queried during rendering. + if (fontData->ligaturePairs && fontData->ligaturePairCount > 0) { + for (uint32_t li = 0; li < fontData->ligaturePairCount && glyphCount < MAX_PAGE_GLYPHS; li++) { + uint32_t leftCp = fontData->ligaturePairs[li].pair >> 16; + uint32_t rightCp = fontData->ligaturePairs[li].pair & 0xFFFF; + + int32_t leftIdx = findGlyphIndex(fontData, leftCp); + int32_t rightIdx = findGlyphIndex(fontData, rightCp); + if (leftIdx < 0 || rightIdx < 0) continue; + + // Check if both inputs are in neededGlyphs + bool hasLeft = false, hasRight = false; + for (uint16_t i = 0; i < glyphCount; i++) { + if (neededGlyphs[i] == static_cast(leftIdx)) hasLeft = true; + if (neededGlyphs[i] == static_cast(rightIdx)) hasRight = true; + if (hasLeft && hasRight) break; + } + if (!hasLeft || !hasRight) continue; + + int32_t outIdx = findGlyphIndex(fontData, fontData->ligaturePairs[li].ligatureCp); + if (outIdx < 0) continue; + + // Deduplicate + bool found = false; + for (uint16_t i = 0; i < glyphCount; i++) { + if (neededGlyphs[i] == static_cast(outIdx)) { + found = true; + break; + } + } + if (!found) { + neededGlyphs[glyphCount++] = static_cast(outIdx); + } + } + } + if (glyphCount == 0) return 0; // Step 2: Compute total buffer size and collect unique groups @@ -305,33 +352,37 @@ int FontDecompressor::prewarmCache(const EpdFontData* fontData, const char* utf8 stats.uniqueGroupsAccessed = groupCount; // Step 3: Allocate page buffer and lookup table - pageBuffer = static_cast(malloc(totalBytes)); - pageGlyphs = static_cast(malloc(glyphCount * sizeof(PageGlyphEntry))); - if (!pageBuffer || !pageGlyphs) { + PageSlot& slot = pageSlots[pageSlotCount]; + slot.buffer = static_cast(malloc(totalBytes)); + slot.glyphs = static_cast(malloc(glyphCount * sizeof(PageGlyphEntry))); + if (!slot.buffer || !slot.glyphs) { LOG_ERR("FDC", "Failed to allocate page buffer (%u bytes, %u glyphs)", totalBytes, glyphCount); - freePageBuffer(); + free(slot.buffer); + free(slot.glyphs); + slot = {}; return glyphCount; } - stats.pageBufferBytes = totalBytes; - stats.pageGlyphsBytes = glyphCount * sizeof(PageGlyphEntry); + stats.pageBufferBytes += totalBytes; + stats.pageGlyphsBytes += glyphCount * sizeof(PageGlyphEntry); - pageFont = fontData; - pageGlyphCount = glyphCount; + slot.fontData = fontData; + slot.glyphCount = glyphCount; + pageSlotCount++; // Initialize lookup entries (bufferOffset = UINT32_MAX means not yet extracted) for (uint16_t i = 0; i < glyphCount; i++) { - pageGlyphs[i] = {neededGlyphs[i], UINT32_MAX, 0}; + slot.glyphs[i] = {neededGlyphs[i], UINT32_MAX, 0}; } // Sort by glyphIndex for binary search in getBitmap() for (uint16_t i = 1; i < glyphCount; i++) { - PageGlyphEntry key = pageGlyphs[i]; + PageGlyphEntry key = slot.glyphs[i]; int j = i - 1; - while (j >= 0 && pageGlyphs[j].glyphIndex > key.glyphIndex) { - pageGlyphs[j + 1] = pageGlyphs[j]; + while (j >= 0 && slot.glyphs[j].glyphIndex > key.glyphIndex) { + slot.glyphs[j + 1] = slot.glyphs[j]; j--; } - pageGlyphs[j + 1] = key; + slot.glyphs[j + 1] = key; } // Step 3b: Pre-scan to compute each needed glyph's byte-aligned offset within its group. @@ -357,15 +408,15 @@ int FontDecompressor::prewarmCache(const EpdFontData* fontData, const char* utf8 const EpdGlyph& glyph = fontData->glyph[i]; - // Binary search in sorted pageGlyphs to find if glyph i is needed - int left = 0, right = (int)pageGlyphCount - 1; + // Binary search in sorted slot.glyphs to find if glyph i is needed + int left = 0, right = (int)slot.glyphCount - 1; while (left <= right) { const int mid = left + (right - left) / 2; - if (pageGlyphs[mid].glyphIndex == i) { - pageGlyphs[mid].alignedOffset = groupAlignedTracker[gpPos]; + if (slot.glyphs[mid].glyphIndex == i) { + slot.glyphs[mid].alignedOffset = groupAlignedTracker[gpPos]; break; } - if (pageGlyphs[mid].glyphIndex < i) + if (slot.glyphs[mid].glyphIndex < i) left = mid + 1; else right = mid - 1; @@ -384,14 +435,14 @@ int FontDecompressor::prewarmCache(const EpdFontData* fontData, const char* utf8 const uint32_t glyphI = group.firstGlyphIndex + j; const EpdGlyph& glyph = fontData->glyph[glyphI]; - int left = 0, right = (int)pageGlyphCount - 1; + int left = 0, right = (int)slot.glyphCount - 1; while (left <= right) { const int mid = left + (right - left) / 2; - if (pageGlyphs[mid].glyphIndex == glyphI) { - pageGlyphs[mid].alignedOffset = alignedOff; + if (slot.glyphs[mid].glyphIndex == glyphI) { + slot.glyphs[mid].alignedOffset = alignedOff; break; } - if (pageGlyphs[mid].glyphIndex < glyphI) + if (slot.glyphs[mid].glyphIndex < glyphI) left = mid + 1; else right = mid - 1; @@ -430,13 +481,13 @@ int FontDecompressor::prewarmCache(const EpdFontData* fontData, const char* utf8 // Extract needed glyphs directly from the byte-aligned temp buffer, compacting on the fly. // alignedOffset was pre-computed in step 3b — no full-group compact scan needed. - for (uint16_t i = 0; i < pageGlyphCount; i++) { - if (pageGlyphs[i].bufferOffset != UINT32_MAX) continue; // already extracted - if (getGroupIndex(fontData, pageGlyphs[i].glyphIndex) != groupIdx) continue; + for (uint16_t i = 0; i < slot.glyphCount; i++) { + if (slot.glyphs[i].bufferOffset != UINT32_MAX) continue; // already extracted + if (getGroupIndex(fontData, slot.glyphs[i].glyphIndex) != groupIdx) continue; - const EpdGlyph& glyph = fontData->glyph[pageGlyphs[i].glyphIndex]; - compactSingleGlyph(&tempBuf[pageGlyphs[i].alignedOffset], &pageBuffer[writeOffset], glyph.width, glyph.height); - pageGlyphs[i].bufferOffset = writeOffset; + const EpdGlyph& glyph = fontData->glyph[slot.glyphs[i].glyphIndex]; + compactSingleGlyph(&tempBuf[slot.glyphs[i].alignedOffset], &slot.buffer[writeOffset], glyph.width, glyph.height); + slot.glyphs[i].bufferOffset = writeOffset; writeOffset += glyph.dataLength; } diff --git a/lib/EpdFont/FontDecompressor.h b/lib/EpdFont/FontDecompressor.h index 7c70cc6e..706bdf74 100644 --- a/lib/EpdFont/FontDecompressor.h +++ b/lib/EpdFont/FontDecompressor.h @@ -9,6 +9,7 @@ class FontDecompressor { public: static constexpr uint16_t MAX_PAGE_GLYPHS = 512; + static constexpr uint8_t MAX_PAGE_SLOTS = 4; FontDecompressor() = default; ~FontDecompressor(); @@ -17,13 +18,13 @@ class FontDecompressor { void deinit(); // Returns pointer to decompressed bitmap data for the given glyph. - // Checks the page buffer (from prewarm) first, then falls back to the hot group slot. + // Checks the page slots (from prewarm) first, then falls back to the hot group slot. const uint8_t* getBitmap(const EpdFontData* fontData, const EpdGlyph* glyph, uint32_t glyphIndex); - // Free all cached data (page buffer + hot group). + // Free all cached data (page slots + hot group). void clearCache(); - // Pre-scan UTF-8 text and extract needed glyph bitmaps into a flat page buffer. + // Pre-scan UTF-8 text and extract needed glyph bitmaps into a flat page slot. // Each group is decompressed once into a temp buffer; only needed glyphs are kept. // Returns the number of glyphs that couldn't be loaded (0 on full success). int prewarmCache(const EpdFontData* fontData, const char* utf8Text); @@ -33,8 +34,8 @@ class FontDecompressor { uint32_t cacheMisses = 0; uint32_t decompressTimeMs = 0; uint16_t uniqueGroupsAccessed = 0; - uint32_t pageBufferBytes = 0; // pageBuffer allocation - uint32_t pageGlyphsBytes = 0; // pageGlyphs lookup table allocation + uint32_t pageBufferBytes = 0; // page slot buffer allocations + uint32_t pageGlyphsBytes = 0; // page slot lookup table allocations uint32_t hotGroupBytes = 0; // current hot group allocation uint32_t peakTempBytes = 0; // largest temp buffer in prewarm uint32_t getBitmapTimeUs = 0; // cumulative getBitmap time (micros) @@ -48,16 +49,20 @@ class FontDecompressor { Stats stats; InflateReader inflateReader; - // Page buffer: flat array of prewarmed glyph bitmaps with sorted lookup + // Page buffers: flat arrays of prewarmed glyph bitmaps with sorted lookup struct PageGlyphEntry { uint32_t glyphIndex; uint32_t bufferOffset; uint32_t alignedOffset; // byte-aligned offset within its decompressed group (set during prewarm pre-scan) }; - uint8_t* pageBuffer = nullptr; - const EpdFontData* pageFont = nullptr; - PageGlyphEntry* pageGlyphs = nullptr; - uint16_t pageGlyphCount = 0; + struct PageSlot { + uint8_t* buffer; + const EpdFontData* fontData; + PageGlyphEntry* glyphs; + uint16_t glyphCount; + }; + PageSlot pageSlots[MAX_PAGE_SLOTS] = {}; + uint8_t pageSlotCount = 0; // Hot group: last decompressed group (byte-aligned) for non-prewarmed fallback path. // Kept in byte-aligned format; individual glyphs are compacted on demand into hotGlyphBuf. diff --git a/lib/EpdFont/SdCardFont.cpp b/lib/EpdFont/SdCardFont.cpp new file mode 100644 index 00000000..df63a2ff --- /dev/null +++ b/lib/EpdFont/SdCardFont.cpp @@ -0,0 +1,1377 @@ +#include "SdCardFont.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include "EpdFontFamily.h" + +static_assert(sizeof(EpdGlyph) == 16, "EpdGlyph must be 16 bytes to match .cpfont file layout"); +static_assert(sizeof(EpdUnicodeInterval) == 12, "EpdUnicodeInterval must be 12 bytes to match .cpfont file layout"); +static_assert(sizeof(EpdKernClassEntry) == 3, "EpdKernClassEntry must be 3 bytes to match .cpfont file layout"); +static_assert(sizeof(EpdLigaturePair) == 8, "EpdLigaturePair must be 8 bytes to match .cpfont file layout"); + +namespace { + +// FNV-1a hash for content-based font ID generation +constexpr uint32_t FNV_OFFSET = 2166136261u; +constexpr uint32_t FNV_PRIME = 16777619u; + +uint32_t fnv1a(const uint8_t* data, size_t len, uint32_t hash = FNV_OFFSET) { + for (size_t i = 0; i < len; i++) { + hash ^= data[i]; + hash *= FNV_PRIME; + } + return hash; +} + +// .cpfont magic bytes +constexpr char CPFONT_MAGIC[8] = {'C', 'P', 'F', 'O', 'N', 'T', '\0', '\0'}; +// CPFONT_VERSION is defined as a #define in SdCardFont.h so it can be +// stringified into FONT_MANIFEST_URL. +constexpr uint32_t HEADER_SIZE = 32; +constexpr uint32_t STYLE_TOC_ENTRY_SIZE = 32; + +// Helper to read little-endian values from byte buffer +inline uint16_t readU16(const uint8_t* p) { return p[0] | (p[1] << 8); } +inline int16_t readI16(const uint8_t* p) { return static_cast(p[0] | (p[1] << 8)); } +inline uint32_t readU32(const uint8_t* p) { return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); } + +// Walks a null-terminated UTF-8 string and appends each unique codepoint to +// codepoints[0..cpCount-1] via O(n²) dedup. Returns true if the buffer +// reached maxCount (cap hit), false if all codepoints fit. +bool collectUniqueCodepoints(const char* text, uint32_t* codepoints, uint32_t& cpCount, uint32_t maxCount) { + const unsigned char* p = reinterpret_cast(text); + while (*p) { + uint32_t cp = utf8NextCodepoint(&p); + if (cp == 0) break; + bool found = false; + for (uint32_t i = 0; i < cpCount; i++) { + if (codepoints[i] == cp) { + found = true; + break; + } + } + if (!found) { + if (cpCount >= maxCount) return true; + codepoints[cpCount++] = cp; + } + } + return false; +} + +const char* asCStr(const std::string& s) { return s.c_str(); } +const char* asCStr(const char* s) { return s; } + +} // namespace + +SdCardFont::~SdCardFont() { freeAll(); } + +// --- Per-style free/cleanup --- + +void SdCardFont::freeStyleMiniData(PerStyle& s) { + delete[] s.miniIntervals; + s.miniIntervals = nullptr; + delete[] s.miniGlyphs; + s.miniGlyphs = nullptr; + delete[] s.miniBitmap; + s.miniBitmap = nullptr; + s.miniIntervalCount = 0; + s.miniGlyphCount = 0; + freeStyleMiniKern(s); + memset(&s.miniData, 0, sizeof(s.miniData)); + s.epdFont.data = &s.stubData; +} + +void SdCardFont::freeStyleKernLigatureData(PerStyle& s) { + delete[] s.kernLeftClasses; + s.kernLeftClasses = nullptr; + delete[] s.kernRightClasses; + s.kernRightClasses = nullptr; + delete[] s.ligaturePairs; + s.ligaturePairs = nullptr; + s.kernLigLoaded = false; + + s.stubData.ligaturePairs = nullptr; + s.stubData.ligaturePairCount = 0; + s.miniData.kernLeftClasses = nullptr; + s.miniData.kernRightClasses = nullptr; + s.miniData.kernMatrix = nullptr; + s.miniData.kernLeftEntryCount = 0; + s.miniData.kernRightEntryCount = 0; + s.miniData.kernLeftClassCount = 0; + s.miniData.kernRightClassCount = 0; + s.miniData.ligaturePairs = nullptr; + s.miniData.ligaturePairCount = 0; +} + +void SdCardFont::freeStyleMiniKern(PerStyle& s) { + delete[] s.miniKernLeftClasses; + s.miniKernLeftClasses = nullptr; + delete[] s.miniKernRightClasses; + s.miniKernRightClasses = nullptr; + delete[] s.miniKernMatrix; + s.miniKernMatrix = nullptr; + s.miniKernLeftEntryCount = 0; + s.miniKernRightEntryCount = 0; + s.miniKernLeftClassCount = 0; + s.miniKernRightClassCount = 0; +} + +void SdCardFont::freeStyleAll(PerStyle& s) { + freeStyleMiniData(s); + delete[] s.fullIntervals; + s.fullIntervals = nullptr; + freeStyleKernLigatureData(s); + s.present = false; +} + +// --- Global free/cleanup --- + +void SdCardFont::freeAll() { + clearOverflow(); + clearPersistentCache(); + for (uint8_t i = 0; i < MAX_STYLES; i++) { + freeStyleAll(styles_[i]); + } + styleCount_ = 0; + contentHash_ = 0; + loaded_ = false; +} + +void SdCardFont::clearOverflow() { + for (uint32_t i = 0; i < overflowCount_; i++) { + delete[] overflow_[i].bitmap; + overflow_[i].bitmap = nullptr; + overflow_[i].codepoint = 0; + } + overflowCount_ = 0; + overflowNext_ = 0; +} + +// --- Per-style kern/ligature --- + +void SdCardFont::applyKernLigaturePointers(PerStyle& s, EpdFontData& data) const { + // Kern data uses the per-page mini tables (renumbered class IDs). The full + // kern matrix is never resident — see PerStyle::miniKernMatrix comment. + data.kernLeftClasses = s.miniKernLeftClasses; + data.kernRightClasses = s.miniKernRightClasses; + data.kernMatrix = s.miniKernMatrix; + data.kernLeftEntryCount = s.miniKernLeftEntryCount; + data.kernRightEntryCount = s.miniKernRightEntryCount; + data.kernLeftClassCount = s.miniKernLeftClassCount; + data.kernRightClassCount = s.miniKernRightClassCount; + // Ligatures are small (typically < 1KB) so they stay resident. + data.ligaturePairs = s.ligaturePairs; + data.ligaturePairCount = s.header.ligaturePairCount; +} + +bool SdCardFont::loadStyleKernLigatureData(PerStyle& s) { + if (s.kernLigLoaded) return true; + bool hasKern = s.header.kernLeftEntryCount > 0; + bool hasLig = s.header.ligaturePairCount > 0; + if (!hasKern && !hasLig) { + s.kernLigLoaded = true; + return true; + } + + FsFile file; + if (!Storage.openFileForRead("SDCF", filePath_, file)) { + LOG_ERR("SDCF", "Failed to open .cpfont for kern/lig: %s", filePath_); + return false; + } + + if (hasKern) { + // Load only the small class-lookup tables (~3KB each). The full matrix + // (~36KB contiguous for Literata) is built per-page from SD in + // buildMiniKernMatrix(). + s.kernLeftClasses = new (std::nothrow) EpdKernClassEntry[s.header.kernLeftEntryCount]; + s.kernRightClasses = new (std::nothrow) EpdKernClassEntry[s.header.kernRightEntryCount]; + + if (!s.kernLeftClasses || !s.kernRightClasses) { + LOG_ERR("SDCF", "Failed to allocate kern classes (%u+%u bytes)", s.header.kernLeftEntryCount * 3u, + s.header.kernRightEntryCount * 3u); + freeStyleKernLigatureData(s); + return false; + } + + if (!file.seekSet(s.kernLeftFileOffset)) { + LOG_ERR("SDCF", "Failed to seek to kern data"); + freeStyleKernLigatureData(s); + return false; + } + size_t leftSz = s.header.kernLeftEntryCount * sizeof(EpdKernClassEntry); + size_t rightSz = s.header.kernRightEntryCount * sizeof(EpdKernClassEntry); + if (file.read(reinterpret_cast(s.kernLeftClasses), leftSz) != static_cast(leftSz) || + file.read(reinterpret_cast(s.kernRightClasses), rightSz) != static_cast(rightSz)) { + LOG_ERR("SDCF", "Failed to read kern classes"); + freeStyleKernLigatureData(s); + return false; + } + } + + if (hasLig) { + s.ligaturePairs = new (std::nothrow) EpdLigaturePair[s.header.ligaturePairCount]; + if (!s.ligaturePairs) { + LOG_ERR("SDCF", "Failed to allocate ligature pairs"); + freeStyleKernLigatureData(s); + return false; + } + if (!file.seekSet(s.ligatureFileOffset)) { + LOG_ERR("SDCF", "Failed to seek to ligature data"); + freeStyleKernLigatureData(s); + return false; + } + size_t sz = s.header.ligaturePairCount * sizeof(EpdLigaturePair); + if (file.read(reinterpret_cast(s.ligaturePairs), sz) != static_cast(sz)) { + LOG_ERR("SDCF", "Failed to read ligature pairs"); + freeStyleKernLigatureData(s); + return false; + } + } + + s.kernLigLoaded = true; + + // Make ligatures visible to the stub (used when no mini data built yet). + // Kern stays nullptr on the stub — it is only wired in miniData via + // applyKernLigaturePointers() after buildMiniKernMatrix() runs. + s.stubData.ligaturePairs = s.ligaturePairs; + s.stubData.ligaturePairCount = s.header.ligaturePairCount; + + LOG_DBG("SDCF", "Kern classes + lig loaded: kernL=%u, kernR=%u, ligs=%u", s.header.kernLeftEntryCount, + s.header.kernRightEntryCount, s.header.ligaturePairCount); + return true; +} + +// --- Per-page mini kern matrix --- + +// Local copy of EpdFont.cpp's lookupKernClass (that one is file-static there). +// Returns the 1-based class ID for `cp`, or 0 if the codepoint has no kerning class. +static uint8_t miniLookupKernClass(const EpdKernClassEntry* entries, uint16_t count, uint32_t cp) { + if (!entries || count == 0 || cp > 0xFFFF) return 0; + const auto target = static_cast(cp); + const auto* end = entries + count; + const auto it = + std::lower_bound(entries, end, target, [](const EpdKernClassEntry& e, uint16_t v) { return e.codepoint < v; }); + return (it != end && it->codepoint == target) ? it->classId : 0; +} + +// Build a small per-page kern matrix containing ONLY the (leftClass, rightClass) +// pairs reachable from codepoints in the current text. Class IDs are renumbered +// to a dense 1..N range so the resulting matrix is usedLeft × usedRight (typical +// Latin page: ~25×25 bytes) instead of the font's full ~180×200 (~36KB). +// +// Correctness: EpdFont::getKerning only touches `kernLeftClasses` / +// `kernRightClasses` / `kernMatrix` / the count fields — we swap all of them to +// the mini versions together in applyKernLigaturePointers, so a codepoint not +// on this page simply returns class 0 (no kerning), which was the pre-existing +// behavior for any codepoint outside the kern classes. +bool SdCardFont::buildMiniKernMatrix(PerStyle& s, const uint32_t* codepoints, uint32_t cpCount) { + freeStyleMiniKern(s); + if (!s.kernLeftClasses || !s.kernRightClasses || s.header.kernLeftEntryCount == 0 || + s.header.kernRightEntryCount == 0) { + return true; // font has no kern classes — nothing to build + } + + // Step 1: mark used left/right classes via a 256-wide bitmap (class IDs are uint8_t). + bool usedLeft[256] = {}; + bool usedRight[256] = {}; + for (uint32_t i = 0; i < cpCount; i++) { + uint8_t lc = miniLookupKernClass(s.kernLeftClasses, s.header.kernLeftEntryCount, codepoints[i]); + if (lc) usedLeft[lc] = true; + uint8_t rc = miniLookupKernClass(s.kernRightClasses, s.header.kernRightEntryCount, codepoints[i]); + if (rc) usedRight[rc] = true; + } + + // Step 2: build renumber maps (oldClassId -> newClassId, 1-based) and + // reverse maps (newClassId -> oldClassId) for the SD read step. + uint8_t leftRenumber[256] = {}; + uint8_t rightRenumber[256] = {}; + uint8_t newToOldLeft[256] = {}; + uint8_t newToOldRight[256] = {}; + uint8_t numLeft = 0, numRight = 0; + for (int i = 1; i < 256; i++) { + if (usedLeft[i]) { + numLeft++; + leftRenumber[i] = numLeft; + newToOldLeft[numLeft] = static_cast(i); + } + if (usedRight[i]) { + numRight++; + rightRenumber[i] = numRight; + newToOldRight[numRight] = static_cast(i); + } + } + if (numLeft == 0 || numRight == 0) { + return true; // no kern pairs applicable on this page + } + + // Step 3: count how many codepoint→classId entries the mini class tables need. + // Each resident class table has one entry per kerned codepoint in the page. + uint16_t miniLeftCount = 0; + uint16_t miniRightCount = 0; + for (uint32_t i = 0; i < cpCount; i++) { + if (miniLookupKernClass(s.kernLeftClasses, s.header.kernLeftEntryCount, codepoints[i]) != 0) miniLeftCount++; + if (miniLookupKernClass(s.kernRightClasses, s.header.kernRightEntryCount, codepoints[i]) != 0) miniRightCount++; + } + + // Step 4: allocate the three mini buffers. The matrix is <1KB in practice + // (<30 × <30 × 1 byte) so fragmentation is a non-issue. + const uint32_t matrixBytes = static_cast(numLeft) * numRight; + s.miniKernLeftClasses = new (std::nothrow) EpdKernClassEntry[miniLeftCount]; + s.miniKernRightClasses = new (std::nothrow) EpdKernClassEntry[miniRightCount]; + s.miniKernMatrix = new (std::nothrow) int8_t[matrixBytes]; + if (!s.miniKernLeftClasses || !s.miniKernRightClasses || !s.miniKernMatrix) { + LOG_ERR("SDCF", "Failed to allocate mini kern (%u+%u+%u bytes)", miniLeftCount * 3u, miniRightCount * 3u, + matrixBytes); + freeStyleMiniKern(s); + return false; + } + + // Step 5: populate mini class tables. `codepoints` is already sorted (see + // prewarm()) so the output is sorted by codepoint — required for binary + // search in lookupKernClass during render. + uint16_t lIdx = 0, rIdx = 0; + for (uint32_t i = 0; i < cpCount; i++) { + uint32_t cp = codepoints[i]; + if (cp > 0xFFFF) continue; // kern class entries are uint16_t + uint8_t lc = miniLookupKernClass(s.kernLeftClasses, s.header.kernLeftEntryCount, cp); + if (lc) { + s.miniKernLeftClasses[lIdx].codepoint = static_cast(cp); + s.miniKernLeftClasses[lIdx].classId = leftRenumber[lc]; + lIdx++; + } + uint8_t rc = miniLookupKernClass(s.kernRightClasses, s.header.kernRightEntryCount, cp); + if (rc) { + s.miniKernRightClasses[rIdx].codepoint = static_cast(cp); + s.miniKernRightClasses[rIdx].classId = rightRenumber[rc]; + rIdx++; + } + } + + // Step 6: read the full matrix's rows for each used left class, keep only + // columns for used right classes. One SD seek + one read per used left class; + // a row is kernRightClassCount bytes (~200 for Literata). + FsFile file; + if (!Storage.openFileForRead("SDCF", filePath_, file)) { + LOG_ERR("SDCF", "Failed to open .cpfont for mini kern: %s", filePath_); + freeStyleMiniKern(s); + return false; + } + + std::unique_ptr rowBuf(new (std::nothrow) int8_t[s.header.kernRightClassCount]); + if (!rowBuf) { + LOG_ERR("SDCF", "Failed to allocate row buffer (%u bytes)", s.header.kernRightClassCount); + freeStyleMiniKern(s); + return false; + } + + for (uint8_t newL = 1; newL <= numLeft; newL++) { + const uint8_t oldL = newToOldLeft[newL]; + const uint32_t rowFileOff = s.kernMatrixFileOffset + (oldL - 1u) * s.header.kernRightClassCount; + if (!file.seekSet(rowFileOff)) { + LOG_ERR("SDCF", "Failed to seek to kern row %u", oldL); + freeStyleMiniKern(s); + return false; + } + if (file.read(reinterpret_cast(rowBuf.get()), s.header.kernRightClassCount) != + static_cast(s.header.kernRightClassCount)) { + LOG_ERR("SDCF", "Failed to read kern row %u", oldL); + freeStyleMiniKern(s); + return false; + } + int8_t* miniRow = s.miniKernMatrix + (newL - 1u) * numRight; + for (uint8_t newR = 1; newR <= numRight; newR++) { + miniRow[newR - 1] = rowBuf[newToOldRight[newR] - 1u]; + } + } + + s.miniKernLeftEntryCount = lIdx; + s.miniKernRightEntryCount = rIdx; + s.miniKernLeftClassCount = numLeft; + s.miniKernRightClassCount = numRight; + + LOG_DBG("SDCF", "Built mini kern: %u×%u matrix (%u bytes, full was %u×%u = %u bytes)", numLeft, numRight, matrixBytes, + s.header.kernLeftClassCount, s.header.kernRightClassCount, + static_cast(s.header.kernLeftClassCount) * s.header.kernRightClassCount); + return true; +} + +// --- Glyph miss callback --- + +void SdCardFont::applyGlyphMissCallback(uint8_t styleIdx) { + overflowCtx_[styleIdx].self = this; + overflowCtx_[styleIdx].styleIdx = styleIdx; + + auto& s = styles_[styleIdx]; + s.stubData.glyphMissHandler = &SdCardFont::onGlyphMiss; + s.stubData.glyphMissCtx = &overflowCtx_[styleIdx]; +} + +// --- Compute per-style file offsets from a base data offset --- + +void SdCardFont::computeStyleFileOffsets(PerStyle& s, uint32_t baseOffset) { + s.intervalsFileOffset = baseOffset; + s.glyphsFileOffset = s.intervalsFileOffset + s.header.intervalCount * sizeof(EpdUnicodeInterval); + s.kernLeftFileOffset = s.glyphsFileOffset + s.header.glyphCount * sizeof(EpdGlyph); + s.kernRightFileOffset = s.kernLeftFileOffset + s.header.kernLeftEntryCount * sizeof(EpdKernClassEntry); + s.kernMatrixFileOffset = s.kernRightFileOffset + s.header.kernRightEntryCount * sizeof(EpdKernClassEntry); + s.ligatureFileOffset = + s.kernMatrixFileOffset + static_cast(s.header.kernLeftClassCount) * s.header.kernRightClassCount; + s.bitmapFileOffset = s.ligatureFileOffset + s.header.ligaturePairCount * sizeof(EpdLigaturePair); +} + +// --- Load --- + +bool SdCardFont::load(const char* path) { + freeAll(); + if (strlen(path) >= sizeof(filePath_)) { + LOG_ERR("SDCF", "Path too long (%zu bytes, max %zu)", strlen(path), sizeof(filePath_) - 1); + return false; + } + strncpy(filePath_, path, sizeof(filePath_) - 1); + filePath_[sizeof(filePath_) - 1] = '\0'; + + FsFile file; + if (!Storage.openFileForRead("SDCF", path, file)) { + LOG_ERR("SDCF", "Failed to open .cpfont: %s", path); + return false; + } + + // Read and validate global header + uint8_t headerBuf[HEADER_SIZE]; + if (file.read(headerBuf, HEADER_SIZE) != HEADER_SIZE) { + LOG_ERR("SDCF", "Failed to read header"); + return false; + } + + if (memcmp(headerBuf, CPFONT_MAGIC, 8) != 0) { + LOG_ERR("SDCF", "Invalid magic bytes"); + return false; + } + + uint16_t fileVersion = readU16(headerBuf + 8); + if (fileVersion != CPFONT_VERSION) { + LOG_ERR("SDCF", "Unsupported version: %u (expected %u)", fileVersion, CPFONT_VERSION); + return false; + } + + // Begin content hash: accumulate global header + uint32_t hash = fnv1a(headerBuf, HEADER_SIZE); + + bool is2Bit = (readU16(headerBuf + 10) & 1) != 0; + + uint8_t styleCount = headerBuf[12]; + if (styleCount == 0 || styleCount > MAX_STYLES) { + LOG_ERR("SDCF", "Invalid style count: %u", styleCount); + return false; + } + + // Read style TOC + for (uint8_t i = 0; i < styleCount; i++) { + uint8_t tocBuf[STYLE_TOC_ENTRY_SIZE]; + if (file.read(tocBuf, STYLE_TOC_ENTRY_SIZE) != STYLE_TOC_ENTRY_SIZE) { + LOG_ERR("SDCF", "Failed to read style TOC entry %u", i); + freeAll(); + return false; + } + + // Accumulate TOC entry into content hash + hash = fnv1a(tocBuf, STYLE_TOC_ENTRY_SIZE, hash); + + uint8_t styleId = tocBuf[0]; + if (styleId >= MAX_STYLES) { + LOG_ERR("SDCF", "Invalid styleId %u in TOC", styleId); + file.close(); + freeAll(); + return false; + } + + auto& s = styles_[styleId]; + s.present = true; + s.header.intervalCount = readU32(tocBuf + 4); + s.header.glyphCount = readU32(tocBuf + 8); + s.header.advanceY = tocBuf[12]; + s.header.ascender = readI16(tocBuf + 13); + s.header.descender = readI16(tocBuf + 15); + s.header.kernLeftEntryCount = readU16(tocBuf + 17); + s.header.kernRightEntryCount = readU16(tocBuf + 19); + s.header.kernLeftClassCount = tocBuf[21]; + s.header.kernRightClassCount = tocBuf[22]; + s.header.ligaturePairCount = tocBuf[23]; + s.header.is2Bit = is2Bit; + + // Sanity-check counts to reject malformed files before allocating. + // Kern class counts are uint8 (bounded by type). Entry counts are uint16 + // but in practice a sane font has far fewer than 4096 per-side kern entries. + static constexpr uint32_t MAX_INTERVALS = 4096; + static constexpr uint32_t MAX_GLYPHS = 65536; + static constexpr uint32_t MAX_KERN_ENTRIES = 4096; + if (s.header.intervalCount > MAX_INTERVALS || s.header.glyphCount > MAX_GLYPHS || + s.header.kernLeftEntryCount > MAX_KERN_ENTRIES || s.header.kernRightEntryCount > MAX_KERN_ENTRIES) { + LOG_ERR("SDCF", "Style %u: unreasonable counts (iv=%u, gl=%u, kL=%u, kR=%u)", styleId, s.header.intervalCount, + s.header.glyphCount, s.header.kernLeftEntryCount, s.header.kernRightEntryCount); + file.close(); + freeAll(); + return false; + } + + uint32_t dataOffset = readU32(tocBuf + 24); + computeStyleFileOffsets(s, dataOffset); + } + + styleCount_ = styleCount; + contentHash_ = hash; + + // Load full intervals into RAM for each present style + for (uint8_t i = 0; i < MAX_STYLES; i++) { + auto& s = styles_[i]; + if (!s.present) continue; + + s.fullIntervals = new (std::nothrow) EpdUnicodeInterval[s.header.intervalCount]; + if (!s.fullIntervals) { + LOG_ERR("SDCF", "Failed to allocate %u intervals for style %u", s.header.intervalCount, i); + freeAll(); + return false; + } + + if (!file.seekSet(s.intervalsFileOffset)) { + LOG_ERR("SDCF", "Failed to seek to intervals for style %u", i); + freeAll(); + return false; + } + size_t intervalsBytes = s.header.intervalCount * sizeof(EpdUnicodeInterval); + if (file.read(reinterpret_cast(s.fullIntervals), intervalsBytes) != static_cast(intervalsBytes)) { + LOG_ERR("SDCF", "Failed to read intervals for style %u", i); + freeAll(); + return false; + } + + // Validate interval contents before any later code (findGlobalGlyphIndex, + // glyph reads) trusts them. A malformed file could otherwise drive + // out-of-range glyph indices into bogus on-disk reads. + { + uint32_t expectedOffset = 0; + uint32_t prevLast = 0; + for (uint32_t j = 0; j < s.header.intervalCount; ++j) { + const auto& iv = s.fullIntervals[j]; + if (iv.first > iv.last) { + LOG_ERR("SDCF", "Style %u: invalid interval %u (first 0x%lX > last 0x%lX)", i, j, + static_cast(iv.first), static_cast(iv.last)); + file.close(); + freeAll(); + return false; + } + const uint32_t span = iv.last - iv.first + 1; + const bool overlapsPrev = (j > 0 && iv.first <= prevLast); + const bool spanTooBig = (span > s.header.glyphCount); + const bool offsetMismatch = (iv.offset != expectedOffset); + const bool offsetOverruns = (iv.offset > s.header.glyphCount - span); + if (overlapsPrev || spanTooBig || offsetMismatch || offsetOverruns) { + LOG_ERR("SDCF", "Style %u: invalid interval layout at %u (overlap=%d span=%u offMis=%d offOver=%d)", i, j, + overlapsPrev, span, offsetMismatch, offsetOverruns); + file.close(); + freeAll(); + return false; + } + expectedOffset += span; + prevLast = iv.last; + } + } + + // Initialize stub data + memset(&s.stubData, 0, sizeof(s.stubData)); + s.stubData.advanceY = s.header.advanceY; + s.stubData.ascender = s.header.ascender; + s.stubData.descender = s.header.descender; + s.stubData.is2Bit = s.header.is2Bit; + + s.epdFont.data = &s.stubData; + applyGlyphMissCallback(i); + } + + loaded_ = true; + + LOG_DBG("SDCF", "Loaded: %s (v%u, %u styles)", path, CPFONT_VERSION, styleCount_); + for (uint8_t i = 0; i < MAX_STYLES; i++) { + if (!styles_[i].present) continue; + const auto& h = styles_[i].header; + LOG_DBG("SDCF", " style[%u]: %u intervals, %u glyphs, advY=%u, asc=%d, desc=%d, kernL=%u, kernR=%u, ligs=%u", i, + h.intervalCount, h.glyphCount, h.advanceY, h.ascender, h.descender, h.kernLeftEntryCount, + h.kernRightEntryCount, h.ligaturePairCount); + } + return true; +} + +// --- Codepoint lookup --- + +int32_t SdCardFont::findGlobalGlyphIndex(const PerStyle& s, uint32_t codepoint) const { + int left = 0; + int right = static_cast(s.header.intervalCount) - 1; + while (left <= right) { + int mid = left + (right - left) / 2; + const auto& interval = s.fullIntervals[mid]; + if (codepoint < interval.first) { + right = mid - 1; + } else if (codepoint > interval.last) { + left = mid + 1; + } else { + return static_cast(interval.offset + (codepoint - interval.first)); + } + } + return -1; +} + +// --- Prewarm --- + +int SdCardFont::prewarm(const char* utf8Text, uint8_t styleMask, bool metadataOnly) { + if (!loaded_) return -1; + styleMask = resolveStyleMask(styleMask); + if (styleMask == 0) return 0; + + unsigned long startMs = millis(); + + // Step 1: Extract unique codepoints from UTF-8 text (shared across all styles). + // Dedup uses O(n^2) linear scan — worst case is MAX_PAGE_GLYPHS (512) unique codepoints + // = ~131K comparisons, but in practice pages contain far fewer unique codepoints so the + // actual cost is much lower. This is dwarfed by SD I/O that follows. Alternatives (hash + // set, bitmap) exceed the 256-byte stack limit or add template bloat. + // Heap-allocated: MAX_PAGE_GLYPHS * 4 = 2048 bytes, too large for stack (limit < 256 bytes) + std::unique_ptr codepoints(new (std::nothrow) uint32_t[MAX_PAGE_GLYPHS]); + if (!codepoints) { + LOG_ERR("SDCF", "Failed to allocate codepoint buffer (%u bytes)", MAX_PAGE_GLYPHS * 4); + return -1; + } + uint32_t cpCount = 0; + + const unsigned char* p = reinterpret_cast(utf8Text); + while (*p && cpCount < MAX_PAGE_GLYPHS) { + uint32_t cp = utf8NextCodepoint(&p); + if (cp == 0) break; + + bool found = false; + for (uint32_t i = 0; i < cpCount; i++) { + if (codepoints[i] == cp) { + found = true; + break; + } + } + if (!found) { + codepoints[cpCount++] = cp; + } + } + + // Always include the replacement character + { + bool hasReplacement = false; + for (uint32_t i = 0; i < cpCount; i++) { + if (codepoints[i] == REPLACEMENT_GLYPH) { + hasReplacement = true; + break; + } + } + if (!hasReplacement && cpCount < MAX_PAGE_GLYPHS) { + codepoints[cpCount++] = REPLACEMENT_GLYPH; + } + } + + // Add ligature output codepoints from all styles being prewarmed. + // Skip during metadata-only prewarm (layout measurement) to avoid loading + // kern/lig data for all styles upfront (~22KB per style). Kern/lig is + // loaded per-style in prewarmStyle() during the full render prewarm instead. + if (!metadataOnly) { + for (uint8_t si = 0; si < MAX_STYLES; si++) { + if (!(styleMask & (1 << si)) || !styles_[si].present) continue; + auto& s = styles_[si]; + + loadStyleKernLigatureData(s); + if (s.ligaturePairs && s.header.ligaturePairCount > 0) { + for (uint8_t li = 0; li < s.header.ligaturePairCount && cpCount < MAX_PAGE_GLYPHS; li++) { + uint32_t leftCp = s.ligaturePairs[li].pair >> 16; + uint32_t rightCp = s.ligaturePairs[li].pair & 0xFFFF; + uint32_t outCp = s.ligaturePairs[li].ligatureCp; + + bool hasLeft = false, hasRight = false; + for (uint32_t i = 0; i < cpCount; i++) { + if (codepoints[i] == leftCp) hasLeft = true; + if (codepoints[i] == rightCp) hasRight = true; + if (hasLeft && hasRight) break; + } + if (!hasLeft || !hasRight) continue; + + bool hasOut = false; + for (uint32_t i = 0; i < cpCount; i++) { + if (codepoints[i] == outCp) { + hasOut = true; + break; + } + } + if (!hasOut) { + codepoints[cpCount++] = outCp; + } + } + } + } + } + + // Sort codepoints for ordered interval building + std::sort(codepoints.get(), codepoints.get() + cpCount); + + // Prewarm each requested style + int totalMissed = 0; + for (uint8_t si = 0; si < MAX_STYLES; si++) { + if (!(styleMask & (1 << si)) || !styles_[si].present) continue; + totalMissed += prewarmStyle(si, codepoints.get(), cpCount, metadataOnly); + } + + stats_.prewarmTotalMs = millis() - startMs; + return totalMissed; +} + +int SdCardFont::prewarmStyle(uint8_t styleIdx, const uint32_t* codepoints, uint32_t cpCount, bool metadataOnly) { + auto& s = styles_[styleIdx]; + + // Map codepoints to global glyph indices for this style + struct CpGlyphMapping { + uint32_t codepoint; + int32_t globalIndex; + }; + CpGlyphMapping* mappings = new (std::nothrow) CpGlyphMapping[cpCount]; + if (!mappings) { + LOG_ERR("SDCF", "Failed to allocate mapping array for style %u", styleIdx); + return static_cast(cpCount); + } + + uint32_t validCount = 0; + for (uint32_t i = 0; i < cpCount; i++) { + int32_t idx = findGlobalGlyphIndex(s, codepoints[i]); + if (idx >= 0) { + mappings[validCount].codepoint = codepoints[i]; + mappings[validCount].globalIndex = idx; + validCount++; + } + } + int missed = static_cast(cpCount - validCount); + + if (validCount == 0) { + freeStyleMiniData(s); + delete[] mappings; + s.epdFont.data = &s.stubData; + return missed; + } + + // Build mini intervals from sorted codepoints + freeStyleMiniData(s); + + uint32_t intervalCapacity = validCount; + s.miniIntervals = new (std::nothrow) EpdUnicodeInterval[intervalCapacity]; + if (!s.miniIntervals) { + LOG_ERR("SDCF", "Failed to allocate mini intervals for style %u", styleIdx); + delete[] mappings; + return static_cast(cpCount); + } + + s.miniIntervalCount = 0; + uint32_t rangeStart = 0; + for (uint32_t i = 1; i <= validCount; i++) { + if (i == validCount || mappings[i].codepoint != mappings[i - 1].codepoint + 1) { + s.miniIntervals[s.miniIntervalCount].first = mappings[rangeStart].codepoint; + s.miniIntervals[s.miniIntervalCount].last = mappings[i - 1].codepoint; + s.miniIntervals[s.miniIntervalCount].offset = rangeStart; + s.miniIntervalCount++; + rangeStart = i; + } + } + + // Allocate mini glyph array + s.miniGlyphCount = validCount; + s.miniGlyphs = new (std::nothrow) EpdGlyph[s.miniGlyphCount]; + if (!s.miniGlyphs) { + LOG_ERR("SDCF", "Failed to allocate mini glyphs for style %u", styleIdx); + delete[] mappings; + freeStyleMiniData(s); + return static_cast(cpCount); + } + + // Build sorted read order for sequential I/O + uint32_t* readOrder = new (std::nothrow) uint32_t[validCount]; + if (!readOrder) { + LOG_ERR("SDCF", "Failed to allocate read order for style %u", styleIdx); + delete[] mappings; + freeStyleMiniData(s); + return static_cast(cpCount); + } + for (uint32_t i = 0; i < validCount; i++) readOrder[i] = i; + std::sort(readOrder, readOrder + validCount, + [&](uint32_t a, uint32_t b) { return mappings[a].globalIndex < mappings[b].globalIndex; }); + + FsFile file; + if (!Storage.openFileForRead("SDCF", filePath_, file)) { + LOG_ERR("SDCF", "Failed to reopen .cpfont for prewarm (style %u)", styleIdx); + delete[] readOrder; + delete[] mappings; + freeStyleMiniData(s); + return static_cast(cpCount); + } + + unsigned long sdStart = millis(); + uint32_t seekCount = 0; + + // Read glyph metadata. lastReadIndex tracks sequential reads to skip redundant + // seeks; INT32_MIN guarantees the first iteration always seeks to the correct + // offset (otherwise when gIdx == 0, the "gIdx != lastReadIndex + 1" check would + // be false and we'd read from the file's current position — the header — which + // decodes to a garbage EpdGlyph with a massive advanceX, inflating any word + // containing that codepoint beyond page width). + int32_t lastReadIndex = INT32_MIN; + for (uint32_t i = 0; i < validCount; i++) { + uint32_t mapIdx = readOrder[i]; + int32_t gIdx = mappings[mapIdx].globalIndex; + + uint32_t fileOff = s.glyphsFileOffset + static_cast(gIdx) * sizeof(EpdGlyph); + if (gIdx != lastReadIndex + 1) { + if (!file.seekSet(fileOff)) { + LOG_ERR("SDCF", "Prewarm: failed to seek to glyph %d (style %u)", gIdx, styleIdx); + file.close(); + delete[] readOrder; + delete[] mappings; + freeStyleMiniData(s); + return static_cast(cpCount); + } + seekCount++; + } + if (file.read(reinterpret_cast(&s.miniGlyphs[mapIdx]), sizeof(EpdGlyph)) != sizeof(EpdGlyph)) { + LOG_ERR("SDCF", "Prewarm: short glyph read (style %u, glyph %d)", styleIdx, gIdx); + delete[] readOrder; + delete[] mappings; + freeStyleMiniData(s); + return static_cast(cpCount); + } + lastReadIndex = gIdx; + } + + uint32_t totalBitmapSize = 0; + + if (!metadataOnly) { + // Compute total bitmap size + for (uint32_t i = 0; i < validCount; i++) { + totalBitmapSize += s.miniGlyphs[i].dataLength; + } + + s.miniBitmap = new (std::nothrow) uint8_t[totalBitmapSize > 0 ? totalBitmapSize : 1]; + if (!s.miniBitmap) { + LOG_ERR("SDCF", "Failed to allocate mini bitmap (%u bytes) for style %u", totalBitmapSize, styleIdx); + delete[] readOrder; + delete[] mappings; + freeStyleMiniData(s); + return static_cast(cpCount); + } + + // Read bitmap data sorted by file offset + std::sort(readOrder, readOrder + validCount, + [&](uint32_t a, uint32_t b) { return s.miniGlyphs[a].dataOffset < s.miniGlyphs[b].dataOffset; }); + + uint32_t miniBitmapOffset = 0; + uint32_t lastBitmapEnd = UINT32_MAX; + for (uint32_t i = 0; i < validCount; i++) { + uint32_t mapIdx = readOrder[i]; + EpdGlyph& glyph = s.miniGlyphs[mapIdx]; + + if (glyph.dataLength == 0) { + glyph.dataOffset = miniBitmapOffset; + continue; + } + + uint32_t fileOff = s.bitmapFileOffset + glyph.dataOffset; + if (fileOff != lastBitmapEnd) { + if (!file.seekSet(fileOff)) { + LOG_ERR("SDCF", "Prewarm: failed to seek to bitmap (style %u)", styleIdx); + file.close(); + delete[] readOrder; + delete[] mappings; + freeStyleMiniData(s); + return static_cast(cpCount); + } + seekCount++; + } + if (file.read(s.miniBitmap + miniBitmapOffset, glyph.dataLength) != static_cast(glyph.dataLength)) { + LOG_ERR("SDCF", "Prewarm: short bitmap read (style %u)", styleIdx); + delete[] readOrder; + delete[] mappings; + freeStyleMiniData(s); + return static_cast(cpCount); + } + lastBitmapEnd = fileOff + glyph.dataLength; + + glyph.dataOffset = miniBitmapOffset; + miniBitmapOffset += glyph.dataLength; + } + } + + uint32_t sdTime = millis() - sdStart; + delete[] readOrder; + delete[] mappings; + + // Full render prewarm: load the persistent kern classes + ligatures (one-time + // per style, small — the big matrix is NOT loaded here) and then build the + // per-page mini kern matrix restricted to class pairs reachable from this + // page's codepoints. Skip during metadata-only prewarm — layout only needs + // advanceX and the mini kern would be thrown away before rendering. + bool kernLigOk = false; + if (!metadataOnly) { + if (loadStyleKernLigatureData(s)) { + kernLigOk = buildMiniKernMatrix(s, codepoints, cpCount); + } + } + + // Populate miniData and swap + memset(&s.miniData, 0, sizeof(s.miniData)); + s.miniData.bitmap = s.miniBitmap; + s.miniData.glyph = s.miniGlyphs; + s.miniData.intervals = s.miniIntervals; + s.miniData.intervalCount = s.miniIntervalCount; + s.miniData.advanceY = s.header.advanceY; + s.miniData.ascender = s.header.ascender; + s.miniData.descender = s.header.descender; + s.miniData.is2Bit = s.header.is2Bit; + if (kernLigOk) { + applyKernLigaturePointers(s, s.miniData); + } + s.miniData.glyphMissHandler = &SdCardFont::onGlyphMiss; + s.miniData.glyphMissCtx = &overflowCtx_[styleIdx]; + + s.epdFont.data = &s.miniData; + + // Accumulate stats + stats_.sdReadTimeMs += sdTime; + stats_.seekCount += seekCount; + stats_.uniqueGlyphs += validCount; + stats_.bitmapBytes += totalBitmapSize; + + return missed; +} + +// --- Cache management --- + +void SdCardFont::clearCache() { + clearOverflow(); + // Note: advance table is intentionally preserved here. It persists across + // layout passes so repeated section indexing amortizes SD reads. Use + // clearPersistentCache() to wipe it. + for (uint8_t i = 0; i < MAX_STYLES; i++) { + if (!styles_[i].present) continue; + freeStyleMiniData(styles_[i]); + applyGlyphMissCallback(i); + } +} + +void SdCardFont::releaseForLowMemory() { + clearOverflow(); + clearPersistentCache(); + + for (uint8_t i = 0; i < MAX_STYLES; i++) { + if (!styles_[i].present) continue; + freeStyleMiniData(styles_[i]); + freeStyleKernLigatureData(styles_[i]); + applyGlyphMissCallback(i); + } +} + +// --- Advance table --- + +void SdCardFont::clearPersistentCache() { + for (uint8_t i = 0; i < MAX_STYLES; i++) { + delete[] advanceTable_[i]; + advanceTable_[i] = nullptr; + advanceTableSize_[i] = 0; + } +} + +bool SdCardFont::advanceTableLookup(uint8_t styleIdx, uint32_t codepoint, uint16_t* outAdvance) const { + const AdvanceEntry* table = advanceTable_[styleIdx]; + const uint32_t size = advanceTableSize_[styleIdx]; + if (!table || size == 0) return false; + uint32_t lo = 0, hi = size; + while (lo < hi) { + uint32_t mid = lo + (hi - lo) / 2; + if (table[mid].codepoint < codepoint) { + lo = mid + 1; + } else { + hi = mid; + } + } + if (lo < size && table[lo].codepoint == codepoint) { + if (outAdvance) *outAdvance = table[lo].advanceX; + return true; + } + return false; +} + +void SdCardFont::mergeIntoAdvanceTable(uint8_t styleIdx, const AdvanceEntry* sortedNew, uint32_t newCount) { + if (newCount == 0) return; + const uint32_t oldSize = advanceTableSize_[styleIdx]; + if (oldSize >= ADVANCE_CACHE_LIMIT) return; // already full + + // Cap the merged size at ADVANCE_CACHE_LIMIT. Anything past the cap is + // dropped from the tail of the sorted merge — a deterministic, bounded loss + // that doesn't bias which codepoints get cached on subsequent passes. + uint32_t mergedCap = oldSize + newCount; + if (mergedCap > ADVANCE_CACHE_LIMIT) mergedCap = ADVANCE_CACHE_LIMIT; + + AdvanceEntry* merged = new (std::nothrow) AdvanceEntry[mergedCap]; + if (!merged) { + LOG_ERR("SDCF", "mergeIntoAdvanceTable: alloc failed (%u entries) style %u", mergedCap, styleIdx); + return; + } + + const AdvanceEntry* a = advanceTable_[styleIdx]; + const AdvanceEntry* b = sortedNew; + uint32_t i = 0, j = 0, k = 0; + while (k < mergedCap && (i < oldSize || j < newCount)) { + if (i < oldSize && (j >= newCount || a[i].codepoint <= b[j].codepoint)) { + merged[k++] = a[i++]; + } else { + merged[k++] = b[j++]; + } + } + + delete[] advanceTable_[styleIdx]; + advanceTable_[styleIdx] = merged; + advanceTableSize_[styleIdx] = k; +} + +bool SdCardFont::hasAdvanceTable() const { + for (uint8_t i = 0; i < MAX_STYLES; i++) { + if (advanceTable_[i]) return true; + } + return false; +} + +uint16_t SdCardFont::getAdvance(uint32_t codepoint, uint8_t style) const { + style &= (MAX_STYLES - 1); + if (!advanceTable_[style]) return 0; + const AdvanceEntry* table = advanceTable_[style]; + const uint32_t size = advanceTableSize_[style]; + // Binary search sorted by codepoint + uint32_t lo = 0, hi = size; + while (lo < hi) { + uint32_t mid = lo + (hi - lo) / 2; + if (table[mid].codepoint < codepoint) { + lo = mid + 1; + } else { + hi = mid; + } + } + if (lo < size && table[lo].codepoint == codepoint) { + return table[lo].advanceX; + } + return 0; +} + +// Given a sorted array of unique codepoints, resolve glyph indices per style, +// batch-read advanceX from SD, and merge into the persistent advance table. +// Caller owns the codepoints buffer. +int SdCardFont::fetchAdvancesForCodepoints(uint32_t* codepoints, uint32_t cpCount, uint8_t styleMask) { + int totalMissed = 0; + for (uint8_t si = 0; si < MAX_STYLES; si++) { + if (!(styleMask & (1 << si)) || !styles_[si].present) continue; + const auto& s = styles_[si]; + + // Stop fetching once the cache is full — further inserts would be dropped + // by the merge anyway. The renderer fast path tolerates missing entries + // (returns 0); the slow path is still correct for those codepoints. + if (advanceTableSize_[si] >= ADVANCE_CACHE_LIMIT) continue; + + // For each codepoint in `codepoints`, skip those already cached, then + // resolve to a glyph index. Build a parallel array sorted by glyph index + // for sequential SD reads. + struct CpIdx { + uint32_t codepoint; + int32_t glyphIndex; + }; + std::unique_ptr mappings(new (std::nothrow) CpIdx[cpCount]); + if (!mappings) { + LOG_ERR("SDCF", "buildAdvanceTable: failed to allocate mappings for style %u", si); + totalMissed += cpCount; + continue; + } + + uint32_t needCount = 0; + uint32_t missedThisStyle = 0; + const int32_t replacementIdx = findGlobalGlyphIndex(s, REPLACEMENT_GLYPH); + for (uint32_t i = 0; i < cpCount; i++) { + const uint32_t cp = codepoints[i]; + if (advanceTableLookup(si, cp, nullptr)) continue; // already cached + int32_t idx = findGlobalGlyphIndex(s, cp); + if (idx < 0) { + if (replacementIdx < 0) { + missedThisStyle++; + continue; + } + idx = replacementIdx; + } + mappings[needCount].codepoint = cp; + mappings[needCount].glyphIndex = idx; + needCount++; + } + totalMissed += static_cast(missedThisStyle); + + if (needCount == 0) continue; + + // Sort by glyph index so SD reads are mostly sequential. + std::sort(mappings.get(), mappings.get() + needCount, + [](const CpIdx& a, const CpIdx& b) { return a.glyphIndex < b.glyphIndex; }); + + // Open file once and read advanceX for each needed glyph. + FsFile file; + if (!Storage.openFileForRead("SDCF", filePath_, file)) { + LOG_ERR("SDCF", "buildAdvanceTable: failed to open .cpfont for style %u", si); + continue; + } + + std::unique_ptr staged(new (std::nothrow) AdvanceEntry[needCount]); + if (!staged) { + LOG_ERR("SDCF", "buildAdvanceTable: failed to allocate staging for style %u", si); + file.close(); + continue; + } + + uint32_t fetched = 0; + EpdGlyph tempGlyph; + int32_t lastReadIndex = INT32_MIN; + for (uint32_t i = 0; i < needCount; i++) { + int32_t gIdx = mappings[i].glyphIndex; + uint32_t fileOff = s.glyphsFileOffset + static_cast(gIdx) * sizeof(EpdGlyph); + if (gIdx != lastReadIndex + 1) { + if (!file.seekSet(fileOff)) { + LOG_ERR("SDCF", "buildAdvanceTable: failed to seek to glyph %d (style %u)", gIdx, si); + break; + } + } + if (file.read(reinterpret_cast(&tempGlyph), sizeof(EpdGlyph)) != sizeof(EpdGlyph)) { + LOG_ERR("SDCF", "buildAdvanceTable: short glyph read (style %u, glyph %d)", si, gIdx); + break; + } + lastReadIndex = gIdx; + staged[fetched].codepoint = mappings[i].codepoint; + staged[fetched].advanceX = tempGlyph.advanceX; + fetched++; + } + file.close(); + + if (fetched > 0) { + // Sort staged by codepoint, then merge into the persistent table. + std::sort(staged.get(), staged.get() + fetched, + [](const AdvanceEntry& a, const AdvanceEntry& b) { return a.codepoint < b.codepoint; }); + mergeIntoAdvanceTable(si, staged.get(), fetched); + } + + LOG_DBG("SDCF", "Advance table style %u: +%u from SD, total=%u/%u", si, fetched, advanceTableSize_[si], + ADVANCE_CACHE_LIMIT); + } + + return totalMissed; +} + +template +int SdCardFont::buildAdvanceTableRange(Iter begin, Iter end, bool includeSpace, bool includeHyphen, uint8_t styleMask) { + if (!loaded_) return -1; + styleMask = resolveStyleMask(styleMask); + if (styleMask == 0) return 0; + + unsigned long startMs = millis(); + + // +2 reserved slots for space and hyphen injected after the main scan. + static constexpr uint32_t MAX_UNIQUE_CODEPOINTS = 4096; + uint32_t* codepoints = new (std::nothrow) uint32_t[MAX_UNIQUE_CODEPOINTS + 2]; + if (!codepoints) { + LOG_ERR("SDCF", "buildAdvanceTable: failed to allocate codepoint buffer (%u bytes)", MAX_UNIQUE_CODEPOINTS * 4); + return -1; + } + uint32_t cpCount = 0; + bool hitCap = false; + + for (auto it = begin; it != end && !hitCap; ++it) { + hitCap = collectUniqueCodepoints(asCStr(*it), codepoints, cpCount, MAX_UNIQUE_CODEPOINTS); + } + + if (includeSpace && std::none_of(codepoints, codepoints + cpCount, [](uint32_t c) { return c == ' '; })) + codepoints[cpCount++] = ' '; + if (includeHyphen && std::none_of(codepoints, codepoints + cpCount, [](uint32_t c) { return c == '-'; })) + codepoints[cpCount++] = '-'; + + if (hitCap) { + LOG_ERR("SDCF", "buildAdvanceTable: unique codepoint cap (%u) hit, layout may be approximate", + MAX_UNIQUE_CODEPOINTS); + } + std::sort(codepoints, codepoints + cpCount); + int totalMissed = fetchAdvancesForCodepoints(codepoints, cpCount, styleMask); + delete[] codepoints; + stats_.prewarmTotalMs = millis() - startMs; + return totalMissed; +} + +int SdCardFont::buildAdvanceTable(const char* utf8Text, uint8_t styleMask) { + return buildAdvanceTableRange(&utf8Text, &utf8Text + 1, false, false, styleMask); +} + +int SdCardFont::buildAdvanceTable(const std::vector& words, bool includeHyphen, uint8_t styleMask) { + return buildAdvanceTableRange(words.begin(), words.end(), words.size() > 1, includeHyphen, styleMask); +} + +// --- Stats --- + +void SdCardFont::logStats(const char* label) { + LOG_DBG("SDCF", "[%s] total=%ums sd_read=%ums seeks=%u glyphs=%u bitmap=%u bytes", label, stats_.prewarmTotalMs, + stats_.sdReadTimeMs, stats_.seekCount, stats_.uniqueGlyphs, stats_.bitmapBytes); +} + +void SdCardFont::resetStats() { stats_ = Stats{}; } + +// --- Public accessors --- + +EpdFont* SdCardFont::getEpdFont(uint8_t style) { + style &= (MAX_STYLES - 1); + if (!styles_[style].present) return nullptr; + return &styles_[style].epdFont; +} + +bool SdCardFont::hasStyle(uint8_t style) const { return styles_[style & (MAX_STYLES - 1)].present; } + +uint8_t SdCardFont::resolveStyle(uint8_t style) const { + static const uint8_t kFallbacks[MAX_STYLES][MAX_STYLES] = { + // REGULAR: REGULAR -> BOLD -> ITALIC -> BOLD_ITALIC + {EpdFontFamily::REGULAR, EpdFontFamily::BOLD, EpdFontFamily::ITALIC, EpdFontFamily::BOLD_ITALIC}, + // BOLD: BOLD -> REGULAR -> BOLD_ITALIC -> ITALIC + {EpdFontFamily::BOLD, EpdFontFamily::REGULAR, EpdFontFamily::BOLD_ITALIC, EpdFontFamily::ITALIC}, + // ITALIC: ITALIC -> REGULAR -> BOLD_ITALIC -> BOLD + {EpdFontFamily::ITALIC, EpdFontFamily::REGULAR, EpdFontFamily::BOLD_ITALIC, EpdFontFamily::BOLD}, + // BOLD_ITALIC: BOLD_ITALIC -> BOLD -> ITALIC -> REGULAR + {EpdFontFamily::BOLD_ITALIC, EpdFontFamily::BOLD, EpdFontFamily::ITALIC, EpdFontFamily::REGULAR}, + }; + + const uint8_t styleBits = style & (MAX_STYLES - 1); + for (uint8_t candidate : kFallbacks[styleBits]) { + if (styles_[candidate].present) return candidate; + } + return EpdFontFamily::REGULAR; +} + +uint8_t SdCardFont::resolveStyleMask(uint8_t styleMask) const { + uint8_t resolvedMask = 0; + for (uint8_t si = 0; si < MAX_STYLES; si++) { + if (styleMask & (1 << si)) { + resolvedMask |= static_cast(1u << resolveStyle(si)); + } + } + return resolvedMask; +} + +// --- On-demand glyph loading (overflow buffer) --- + +const EpdGlyph* SdCardFont::onGlyphMiss(void* ctx, uint32_t codepoint) { + auto* oc = static_cast(ctx); + auto* self = oc->self; + uint8_t styleIdx = oc->styleIdx; + + if (!self->loaded_ || styleIdx >= MAX_STYLES || !self->styles_[styleIdx].present) return nullptr; + const auto& s = self->styles_[styleIdx]; + if (!s.fullIntervals) return nullptr; + + // Check overflow cache first (matching both codepoint and style) + for (uint32_t i = 0; i < self->overflowCount_; i++) { + if (self->overflow_[i].codepoint == codepoint && self->overflow_[i].styleIdx == styleIdx) { + return &self->overflow_[i].glyph; + } + } + + // Look up global glyph index via full intervals + int32_t globalIdx = self->findGlobalGlyphIndex(s, codepoint); + if (globalIdx < 0) return nullptr; + + // Pick overflow slot (ring buffer). Read into temporaries first so the + // existing slot stays valid if SD I/O fails. Bookkeeping (count/next) + // is deferred until after all I/O succeeds to avoid inconsistent state. + uint32_t slot = self->overflowNext_; + bool wasAtCapacity = (self->overflowCount_ == OVERFLOW_CAPACITY); + + // Read glyph metadata into temporary + FsFile file; + if (!Storage.openFileForRead("SDCF", self->filePath_, file)) { + LOG_ERR("SDCF", "Overflow: failed to open .cpfont"); + return nullptr; + } + + EpdGlyph tempGlyph = {}; + uint32_t glyphFileOff = s.glyphsFileOffset + static_cast(globalIdx) * sizeof(EpdGlyph); + if (!file.seekSet(glyphFileOff)) { + LOG_ERR("SDCF", "Overflow: failed to seek to glyph for U+%04X style %u", codepoint, styleIdx); + file.close(); + return nullptr; + } + if (file.read(reinterpret_cast(&tempGlyph), sizeof(EpdGlyph)) != sizeof(EpdGlyph)) { + LOG_ERR("SDCF", "Overflow: failed to read glyph metadata for U+%04X style %u", codepoint, styleIdx); + return nullptr; + } + + // Read bitmap data into temporary (if any) + uint8_t* tempBitmap = nullptr; + if (tempGlyph.dataLength > 0) { + tempBitmap = new (std::nothrow) uint8_t[tempGlyph.dataLength]; + if (!tempBitmap) { + LOG_ERR("SDCF", "Overflow: failed to allocate %u bytes for U+%04X bitmap", tempGlyph.dataLength, codepoint); + return nullptr; + } + if (!file.seekSet(s.bitmapFileOffset + tempGlyph.dataOffset)) { + LOG_ERR("SDCF", "Overflow: failed to seek to bitmap for U+%04X", codepoint); + delete[] tempBitmap; + file.close(); + return nullptr; + } + if (file.read(tempBitmap, tempGlyph.dataLength) != static_cast(tempGlyph.dataLength)) { + LOG_ERR("SDCF", "Overflow: failed to read bitmap for U+%04X", codepoint); + delete[] tempBitmap; + return nullptr; + } + } + + // All reads succeeded — commit to slot and advance ring buffer + if (wasAtCapacity) { + delete[] self->overflow_[slot].bitmap; + } else { + self->overflowCount_++; + } + self->overflowNext_ = (slot + 1) % OVERFLOW_CAPACITY; + self->overflow_[slot].glyph = tempGlyph; + self->overflow_[slot].bitmap = tempBitmap; + self->overflow_[slot].codepoint = codepoint; + self->overflow_[slot].styleIdx = styleIdx; + + LOG_DBG("SDCF", "Overflow: loaded U+%04X style %u on demand (slot %u/%u)", codepoint, styleIdx, slot, + OVERFLOW_CAPACITY); + + return &self->overflow_[slot].glyph; +} + +bool SdCardFont::isOverflowGlyph(const EpdGlyph* glyph) const { + for (uint32_t i = 0; i < overflowCount_; i++) { + if (&overflow_[i].glyph == glyph) return true; + } + return false; +} + +const uint8_t* SdCardFont::getOverflowBitmap(const EpdGlyph* glyph) const { + for (uint32_t i = 0; i < overflowCount_; i++) { + if (&overflow_[i].glyph == glyph) { + return overflow_[i].bitmap; + } + } + return nullptr; +} + +SdCardFont* SdCardFont::fromMissCtx(void* ctx) { return static_cast(ctx)->self; } diff --git a/lib/EpdFont/SdCardFont.h b/lib/EpdFont/SdCardFont.h new file mode 100644 index 00000000..378bbfa8 --- /dev/null +++ b/lib/EpdFont/SdCardFont.h @@ -0,0 +1,261 @@ +#pragma once + +#include +#include +#include + +#include "EpdFont.h" +#include "EpdFontData.h" + +// On-disk binary format version for .cpfont files. Defined as a preprocessor +// macro (rather than a constexpr) so it can be stringified into the SD-fonts +// release URL — see FONT_MANIFEST_URL in FontDownloadActivity.h. No integer +// suffix because stringification would include it (e.g. `4U` → `"4U"`). +// +// The canonical version for the build tooling lives in +// lib/EpdFont/scripts/cpfont_version.py. This firmware-side copy must be +// bumped manually when the firmware is updated to support a new format. +// Reader enforcement: SdCardFont::load(). +#define CPFONT_VERSION 4 + +class SdCardFont { + public: + static constexpr uint16_t MAX_PAGE_GLYPHS = 512; + static constexpr uint8_t MAX_STYLES = 4; + + SdCardFont() = default; + ~SdCardFont(); + // Owns raw buffers freed in dtor — no shallow-copy semantics. Make any + // accidental pass-by-value or move a compile-time error. + SdCardFont(const SdCardFont&) = delete; + SdCardFont& operator=(const SdCardFont&) = delete; + SdCardFont(SdCardFont&&) = delete; + SdCardFont& operator=(SdCardFont&&) = delete; + + // Load .cpfont file: reads header + intervals into RAM, records file layout offsets. + // Supports v4 (multi-style) format. + // Returns true on success. + bool load(const char* path); + + // Pre-read glyphs needed for the given UTF-8 text from SD card. + // styleMask: bitmask of styles to prewarm (bit 0=regular, 1=bold, 2=italic, 3=bolditalic). + // Default 0x0F = all present styles. + // When metadataOnly=true, only glyph metrics are loaded (no bitmap data). + // Returns number of glyphs that couldn't be loaded (0 on full success). + int prewarm(const char* utf8Text, uint8_t styleMask = 0x0F, bool metadataOnly = false); + + // Build a compact advance-only table for layout measurement. + // Extracts ALL unique codepoints from words (no MAX_PAGE_GLYPHS cap), + // batch-reads advanceX from SD, stores in a sorted per-style table. + // Returns number of codepoints not found in font coverage. + int buildAdvanceTable(const char* utf8Text, uint8_t styleMask = 0x0F); + int buildAdvanceTable(const std::vector& words, bool includeHyphen, uint8_t styleMask = 0x0F); + + // Look up advanceX for a codepoint from the advance table. + // Returns the 12.4 fixed-point advance, or 0 if not found. + uint16_t getAdvance(uint32_t codepoint, uint8_t style) const; + + // Returns true if advance table is populated for at least one style. + bool hasAdvanceTable() const; + + // Free mini data for all styles, restore stub EpdFontData. + // Also clears the temporary advance table (built per layout pass) but + // preserves the persistent advance cache (reused across passes). + void clearCache(); + + // Release optional resident caches before memory-heavy work such as EPUB + // image extraction. Keeps the font loaded and usable, but future layout or + // rendering may need to re-read font metadata from SD. + void releaseForLowMemory(); + + // Drop the persistent advance cache. Call when unloading the SD font or + // when font/size/family/glyph-table state changes. + void clearPersistentCache(); + + // Returns pointer to the managed EpdFont for a given style. + // Returns nullptr if the style is not present. + EpdFont* getEpdFont(uint8_t style = 0); + + // Returns true if the given style is present in this font file. + bool hasStyle(uint8_t style) const; + + // Resolve requested style bits to the closest present style. + uint8_t resolveStyle(uint8_t style) const; + + // Resolve every requested style bit through fallback and return the actual + // styles that need cache/advance preparation. + uint8_t resolveStyleMask(uint8_t styleMask) const; + + // Number of styles present in this font file. + uint8_t styleCount() const { return styleCount_; } + + // Returns true if the glyph pointer points into the overflow buffer. + bool isOverflowGlyph(const EpdGlyph* glyph) const; + + // Returns the bitmap for an on-demand-loaded (overflow) glyph. + const uint8_t* getOverflowBitmap(const EpdGlyph* glyph) const; + + // Extract SdCardFont* from an opaque glyphMissCtx pointer. + // Used by GfxRenderer::getGlyphBitmap() to recover the SdCardFont from EpdFontData::glyphMissCtx. + static SdCardFont* fromMissCtx(void* ctx); + + struct Stats { + uint32_t prewarmTotalMs = 0; + uint32_t sdReadTimeMs = 0; + uint32_t seekCount = 0; + uint32_t uniqueGlyphs = 0; + uint32_t bitmapBytes = 0; + }; + void logStats(const char* label = "SDCF"); + void resetStats(); + const Stats& getStats() const { return stats_; } + + // Content hash of the file header + style TOC entries (computed during load). + // Used to generate deterministic font IDs for section cache invalidation. + uint32_t contentHash() const { return contentHash_; } + + private: + // Per-style metadata (parsed from file header/TOC) + struct CpFontHeader { + uint32_t intervalCount = 0; + uint32_t glyphCount = 0; + uint8_t advanceY = 0; + int16_t ascender = 0; + int16_t descender = 0; + bool is2Bit = false; + uint16_t kernLeftEntryCount = 0; + uint16_t kernRightEntryCount = 0; + uint8_t kernLeftClassCount = 0; + uint8_t kernRightClassCount = 0; + uint8_t ligaturePairCount = 0; + }; + + // All per-style data: file offsets, intervals, kern/lig, prewarm cache, EpdFont + struct PerStyle { + CpFontHeader header{}; + + // File layout offsets for this style's data sections + uint32_t intervalsFileOffset = 0; + uint32_t glyphsFileOffset = 0; + uint32_t kernLeftFileOffset = 0; + uint32_t kernRightFileOffset = 0; + uint32_t kernMatrixFileOffset = 0; + uint32_t ligatureFileOffset = 0; + uint32_t bitmapFileOffset = 0; + + // Full intervals loaded from file (kept in RAM for codepoint lookup) + EpdUnicodeInterval* fullIntervals = nullptr; + + // Persistent kern-class + ligature tables (lazy-loaded on first prewarm). + // The full kern MATRIX is NOT resident — on Literata-class fonts a single + // style's matrix is ~36-42KB contiguous, and 4 styles' worth won't fit + // alongside bitmaps + framebuffer on a 380KB device. Only kernLeftClasses + // and kernRightClasses (small codepoint→classId tables, ~3KB each) stay + // resident; the matrix is reconstructed per-page as miniKernMatrix. + EpdKernClassEntry* kernLeftClasses = nullptr; + EpdKernClassEntry* kernRightClasses = nullptr; + EpdLigaturePair* ligaturePairs = nullptr; + bool kernLigLoaded = false; + + // Stub EpdFontData returned when not prewarmed + EpdFontData stubData{}; + + // Mini EpdFontData built during prewarm + EpdFontData miniData{}; + EpdUnicodeInterval* miniIntervals = nullptr; + EpdGlyph* miniGlyphs = nullptr; + uint8_t* miniBitmap = nullptr; + uint32_t miniIntervalCount = 0; + uint32_t miniGlyphCount = 0; + + // Per-page mini kern matrix (built by buildMiniKernMatrix on each full + // prewarm). miniKernLeftClasses/miniKernRightClasses map ONLY the codepoints + // used on the current page to renumbered class IDs (1..miniKern*ClassCount). + // miniKernMatrix is a small miniKernLeftClassCount × miniKernRightClassCount + // flat matrix. Typical Latin page: ~25×25 matrix = ~625 bytes per style vs + // ~36KB for the full Literata matrix — ~50× reduction. + EpdKernClassEntry* miniKernLeftClasses = nullptr; + EpdKernClassEntry* miniKernRightClasses = nullptr; + uint16_t miniKernLeftEntryCount = 0; + uint16_t miniKernRightEntryCount = 0; + uint8_t miniKernLeftClassCount = 0; + uint8_t miniKernRightClassCount = 0; + int8_t* miniKernMatrix = nullptr; + + // The EpdFont whose data pointer we manage + EpdFont epdFont{&stubData}; + + bool present = false; + }; + + PerStyle styles_[MAX_STYLES] = {}; + uint8_t styleCount_ = 0; + + char filePath_[128] = {}; + + // Overflow context: glyphMissHandler needs to know which style it's serving + struct OverflowContext { + SdCardFont* self; + uint8_t styleIdx; + }; + OverflowContext overflowCtx_[MAX_STYLES] = {}; + + // Shared on-demand overflow buffer (ring buffer of glyphs loaded via glyphMissHandler) + static constexpr uint32_t OVERFLOW_CAPACITY = 8; + struct OverflowEntry { + EpdGlyph glyph; + uint8_t* bitmap = nullptr; + uint32_t codepoint = 0; + uint8_t styleIdx = 0; + }; + OverflowEntry overflow_[OVERFLOW_CAPACITY] = {}; + uint32_t overflowCount_ = 0; + uint32_t overflowNext_ = 0; + + // Compact advance-only table for layout measurement (per-style). + // Built by buildAdvanceTable(), queried by getAdvance(). + struct AdvanceEntry { + uint32_t codepoint; + uint16_t advanceX; // 12.4 fixed-point + }; + // Per-style advance table. Sorted by codepoint for binary lookup. + // Bounded to ADVANCE_CACHE_LIMIT entries; persists across layout passes + // (across calls to clearCache()) so repeated indexing of the same font + // amortizes SD reads. Cleared only on font unload or clearPersistentCache(). + // Keep this conservative: the cache is resident per style, so large caps + // quickly eat heap on the ESP32-C3. + static constexpr uint32_t ADVANCE_CACHE_LIMIT = 256; + AdvanceEntry* advanceTable_[MAX_STYLES] = {}; + uint32_t advanceTableSize_[MAX_STYLES] = {}; + bool advanceTableLookup(uint8_t styleIdx, uint32_t codepoint, uint16_t* outAdvance) const; + // Merge sortedNew (sorted by codepoint, no overlap with existing) into the + // advance table for styleIdx, preserving sort order; cap-truncates the tail. + void mergeIntoAdvanceTable(uint8_t styleIdx, const AdvanceEntry* sortedNew, uint32_t newCount); + + Stats stats_; + uint32_t contentHash_ = 0; + bool loaded_ = false; + + // Per-style helpers + void freeStyleMiniData(PerStyle& s); + void freeStyleAll(PerStyle& s); + void freeStyleKernLigatureData(PerStyle& s); + void freeStyleMiniKern(PerStyle& s); + bool loadStyleKernLigatureData(PerStyle& s); + bool buildMiniKernMatrix(PerStyle& s, const uint32_t* codepoints, uint32_t cpCount); + void applyKernLigaturePointers(PerStyle& s, EpdFontData& data) const; + void applyGlyphMissCallback(uint8_t styleIdx); + int32_t findGlobalGlyphIndex(const PerStyle& s, uint32_t codepoint) const; + int fetchAdvancesForCodepoints(uint32_t* codepoints, uint32_t cpCount, uint8_t styleMask); + template + int buildAdvanceTableRange(Iter begin, Iter end, bool includeSpace, bool includeHyphen, uint8_t styleMask); + int prewarmStyle(uint8_t styleIdx, const uint32_t* codepoints, uint32_t cpCount, bool metadataOnly); + + // Global helpers + void freeAll(); + void clearOverflow(); + static void computeStyleFileOffsets(PerStyle& s, uint32_t baseOffset); + + // Static callback for EpdFontData::glyphMissHandler (per-style via OverflowContext) + static const EpdGlyph* onGlyphMiss(void* ctx, uint32_t codepoint); +}; diff --git a/lib/EpdFont/SdCardFontManager.cpp b/lib/EpdFont/SdCardFontManager.cpp new file mode 100644 index 00000000..a6032336 --- /dev/null +++ b/lib/EpdFont/SdCardFontManager.cpp @@ -0,0 +1,98 @@ +#include "SdCardFontManager.h" + +#include +#include +#include +#include +#include + +SdCardFontManager::~SdCardFontManager() { + for (auto& lf : loaded_) { + delete lf.font; + } +} + +// FNV-1a continuation: seeds with contentHash, then hashes family name + point size. +// Produces a deterministic ID that is stable across load/unload cycles and reboots, +// and changes when font content changes (different header/TOC = different contentHash). +int SdCardFontManager::computeFontId(uint32_t contentHash, const char* familyName, uint8_t pointSize) { + static constexpr uint32_t FNV_PRIME = 16777619u; + uint32_t hash = contentHash; + while (*familyName) { + hash ^= static_cast(*familyName++); + hash *= FNV_PRIME; + } + hash ^= pointSize; + hash *= FNV_PRIME; + int id = static_cast(hash); + return id != 0 ? id : 1; // 0 is reserved as "not found" sentinel +} + +bool SdCardFontManager::loadFamily(const SdCardFontFamilyInfo& family, GfxRenderer& renderer, uint8_t fontSizeEnum) { + // Unload any previously loaded family first + if (!loadedFamilyName_.empty()) { + unloadAll(renderer); + } + + // Select by ordinal position: sort available sizes, then map the font size + // enum (SMALL=0 .. EXTRA_LARGE=3) to the corresponding slot. When the + // family has fewer sizes than 4, clamp to the last available size. + auto sizes = family.availableSizes(); + if (sizes.empty()) { + LOG_ERR("SDMGR", "Family %s has no files to load", family.name.c_str()); + return false; + } + + uint8_t idx = fontSizeEnum; + if (idx >= sizes.size()) idx = sizes.size() - 1; + const SdCardFontFileInfo* selected = family.findFile(sizes[idx]); + + auto* font = new (std::nothrow) SdCardFont(); + if (!font) { + LOG_ERR("SDMGR", "Failed to allocate SdCardFont for %s", selected->path.c_str()); + return false; + } + + if (!font->load(selected->path.c_str())) { + LOG_ERR("SDMGR", "Failed to load %s", selected->path.c_str()); + delete font; + return false; + } + + int fontId = computeFontId(font->contentHash(), family.name.c_str(), selected->pointSize); + // Guard against collision with built-in font IDs (astronomically unlikely + // with FNV-1a hashes, but provides a safety net) + if (renderer.getFontMap().count(fontId) != 0) { + LOG_ERR("SDMGR", "Font ID %d collides with existing font, skipping %s", fontId, selected->path.c_str()); + delete font; + return false; + } + renderer.registerSdCardFont(fontId, font); + loaded_.push_back({font, fontId, selected->pointSize}); + + LOG_DBG("SDMGR", "Loaded %s size=%u id=%d styles=%u (sizeEnum=%u)", selected->path.c_str(), selected->pointSize, + fontId, font->styleCount(), fontSizeEnum); + + EpdFontFamily fontFamily(font->getEpdFont(0), font->getEpdFont(1), font->getEpdFont(2), font->getEpdFont(3)); + renderer.insertFont(fontId, fontFamily); + + loadedFamilyName_ = family.name; + loadedPointSize_ = selected->pointSize; + return true; +} + +void SdCardFontManager::unloadAll(GfxRenderer& renderer) { + renderer.clearSdCardFonts(); + for (auto& lf : loaded_) { + renderer.removeFont(lf.fontId); + delete lf.font; + } + loaded_.clear(); + loadedFamilyName_.clear(); + loadedPointSize_ = 0; +} + +int SdCardFontManager::getFontId(const std::string& familyName) const { + if (familyName != loadedFamilyName_ || loaded_.empty()) return 0; + return loaded_.front().fontId; +} diff --git a/lib/EpdFont/SdCardFontManager.h b/lib/EpdFont/SdCardFontManager.h new file mode 100644 index 00000000..aec07472 --- /dev/null +++ b/lib/EpdFont/SdCardFontManager.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include + +class GfxRenderer; +class SdCardFont; +struct SdCardFontFamilyInfo; + +class SdCardFontManager { + public: + SdCardFontManager() = default; + ~SdCardFontManager(); + SdCardFontManager(const SdCardFontManager&) = delete; + SdCardFontManager& operator=(const SdCardFontManager&) = delete; + + // Load the font file matching fontSizeEnum (SMALL=0 .. EXTRA_LARGE=3) by + // ordinal position in the family's sorted size list. Only one .cpfont file + // is loaded; other sizes remain on disk. This keeps resident interval + + // kern/ligature tables to one size's worth of memory. + // Returns true on success. + bool loadFamily(const SdCardFontFamilyInfo& family, GfxRenderer& renderer, uint8_t fontSizeEnum); + + // Unload everything, unregister from renderer. + void unloadAll(GfxRenderer& renderer); + + // Look up the font ID for the loaded family. Returns 0 if nothing loaded + // or familyName doesn't match. + int getFontId(const std::string& familyName) const; + + // Get name of currently loaded family (empty if none). + const std::string& currentFamilyName() const { return loadedFamilyName_; }; + + // Point size that was actually loaded (closest match to targetPtSize). + // 0 if nothing loaded. + uint8_t currentPointSize() const { return loadedPointSize_; }; + + private: + struct LoadedFont { + SdCardFont* font; // heap-allocated, owned + int fontId; + uint8_t size; + }; + static int computeFontId(uint32_t contentHash, const char* familyName, uint8_t pointSize); + + std::string loadedFamilyName_; + uint8_t loadedPointSize_ = 0; + std::vector loaded_; +}; diff --git a/lib/EpdFont/SdCardFontRegistry.cpp b/lib/EpdFont/SdCardFontRegistry.cpp new file mode 100644 index 00000000..2e0f145c --- /dev/null +++ b/lib/EpdFont/SdCardFontRegistry.cpp @@ -0,0 +1,230 @@ +#include "SdCardFontRegistry.h" + +#include +#include + +#include +#include + +// --- SdCardFontFamilyInfo helpers --- + +const SdCardFontFileInfo* SdCardFontFamilyInfo::findFile(uint8_t size, uint8_t style) const { + for (const auto& f : files) { + if (f.pointSize == size && f.style == style) return &f; + } + return nullptr; +} + +bool SdCardFontFamilyInfo::hasSize(uint8_t size) const { + for (const auto& f : files) { + if (f.pointSize == size) return true; + } + return false; +} + +std::vector SdCardFontFamilyInfo::availableSizes() const { + std::vector sizes; + for (const auto& f : files) { + bool found = false; + for (uint8_t s : sizes) { + if (s == f.pointSize) { + found = true; + break; + } + } + if (!found) sizes.push_back(f.pointSize); + } + std::sort(sizes.begin(), sizes.end()); + return sizes; +} + +// --- SdCardFontRegistry --- + +bool SdCardFontRegistry::parseFilename(const char* filename, uint8_t& size, uint8_t& style) { + // V4 naming: _.cpfont (e.g. Bookerly-SD_14.cpfont) + // Use an ends-with check rather than strstr() so that in-progress downloads + // like "Foo_14.cpfont.tmp" or backups like "Foo_14.cpfont~" aren't accepted. + static constexpr char kExt[] = ".cpfont"; + static constexpr size_t kExtLen = sizeof(kExt) - 1; + const size_t nameLen = strlen(filename); + if (nameLen <= kExtLen) return false; + if (strcmp(filename + nameLen - kExtLen, kExt) != 0) return false; + const char* ext = filename + nameLen - kExtLen; + + size_t baseLen = ext - filename; + if (baseLen == 0 || baseLen > 127) return false; + + char base[128]; + memcpy(base, filename, baseLen); + base[baseLen] = '\0'; + + char* lastUnderscore = strrchr(base, '_'); + if (!lastUnderscore || lastUnderscore == base) return false; + + const char* sizeStr = lastUnderscore + 1; + char* endPtr; + long sizeVal = strtol(sizeStr, &endPtr, 10); + if (endPtr == sizeStr || *endPtr != '\0' || sizeVal < 1 || sizeVal > 255) return false; + size = static_cast(sizeVal); + // V4 .cpfont files bundle every style (regular/bold/italic/bold-italic) into + // one file, so style is always 0 at the registry level. The per-style + // bitstream is selected later by SdCardFont::getEpdFont(style). The `style` + // field in SdCardFontFileInfo is reserved for future formats that split + // styles across files; scanDirectory() defends against accidental + // (pointSize, style) collisions in that scenario. + style = 0; + return true; +} + +void SdCardFontRegistry::scanDirectory(const char* dirPath, SdCardFontFamilyInfo& family) { + FsFile dir = Storage.open(dirPath); + if (!dir || !dir.isDirectory()) return; + + char nameBuffer[128]; + while (true) { + FsFile entry = dir.openNextFile(); + if (!entry) break; + if (entry.isDirectory()) { + entry.close(); + continue; + } + + entry.getName(nameBuffer, sizeof(nameBuffer)); + entry.close(); + + // Skip macOS resource fork files (._*) and other hidden files + if (nameBuffer[0] == '.' || nameBuffer[0] == '_') continue; + + uint8_t size, style; + if (!parseFilename(nameBuffer, size, style)) continue; + + // Reject duplicate (pointSize, style) entries in the same family. With + // v4's bundle-everything design parseFilename always returns style=0, so + // two files at the same size in the same family would silently shadow + // each other in findFile(). Skip the duplicate and warn. + bool duplicate = false; + for (const auto& existing : family.files) { + if (existing.pointSize == size && existing.style == style) { + duplicate = true; + break; + } + } + if (duplicate) { + LOG_ERR("SDREG", "Duplicate font %s in %s — skipping", nameBuffer, dirPath); + continue; + } + + SdCardFontFileInfo info; + info.path = std::string(dirPath) + "/" + nameBuffer; + info.pointSize = size; + info.style = style; + family.files.push_back(std::move(info)); + } +} + +// Scan a single root (e.g. "/.fonts") and append its families to `out`. +// Skips families whose names already exist in `out` (de-duplicates between +// the hidden and visible roots — first scan wins). +void SdCardFontRegistry::scanRoot(const char* rootPath, std::vector& out) { + FsFile root = Storage.open(rootPath); + if (!root) { + LOG_DBG("SDREG", "Fonts directory not found: %s", rootPath); + return; + } + if (!root.isDirectory()) { + LOG_ERR("SDREG", "Fonts path is not a directory: %s", rootPath); + return; + } + + char nameBuffer[128]; + while (true) { + FsFile entry = root.openNextFile(); + if (!entry) break; + if (entry.isDirectory()) { + entry.getName(nameBuffer, sizeof(nameBuffer)); + entry.close(); + + // Skip hidden/system directories inside the root (macOS ._*, .Trashes, etc.) + if (nameBuffer[0] == '.' || nameBuffer[0] == '_') continue; + + // De-dup by family name across roots. + bool exists = false; + for (const auto& fam : out) { + if (fam.name == nameBuffer) { + exists = true; + break; + } + } + if (exists) continue; + + SdCardFontFamilyInfo family; + family.name = nameBuffer; + std::string subDirPath = std::string(rootPath) + "/" + nameBuffer; + SdCardFontRegistry::scanDirectory(subDirPath.c_str(), family); + + if (!family.files.empty()) { + out.push_back(std::move(family)); + LOG_DBG("SDREG", "Found family: %s (%d files) in %s", out.back().name.c_str(), + static_cast(out.back().files.size()), rootPath); + } + } else { + entry.close(); + } + } +} + +bool SdCardFontRegistry::discover() { + families_.clear(); + families_.reserve(MAX_SD_FAMILIES); + + // Hidden root is scanned first so it wins on name collisions, matching the + // sleep-folder pattern (/.sleep preferred over /sleep). + scanRoot(FONTS_DIR_HIDDEN, families_); + scanRoot(FONTS_DIR_VISIBLE, families_); + + // Sort families alphabetically + std::sort(families_.begin(), families_.end(), + [](const SdCardFontFamilyInfo& a, const SdCardFontFamilyInfo& b) { return a.name < b.name; }); + + // Cap at MAX_SD_FAMILIES + if (static_cast(families_.size()) > MAX_SD_FAMILIES) { + families_.resize(MAX_SD_FAMILIES); + } + + LOG_DBG("SDREG", "Discovery complete: %d families", static_cast(families_.size())); + return !families_.empty(); +} + +const char* SdCardFontRegistry::findFamilyRoot(const char* familyName) { + if (!familyName || !*familyName) return nullptr; + char path[160]; + snprintf(path, sizeof(path), "%s/%s", FONTS_DIR_HIDDEN, familyName); + if (Storage.exists(path)) return FONTS_DIR_HIDDEN; + snprintf(path, sizeof(path), "%s/%s", FONTS_DIR_VISIBLE, familyName); + if (Storage.exists(path)) return FONTS_DIR_VISIBLE; + return nullptr; +} + +const char* SdCardFontRegistry::defaultWriteRoot() { + // If exactly one of the roots already exists, keep using it. Otherwise + // (neither exists, or both exist) prefer the hidden root for new installs. + bool hiddenExists = Storage.exists(FONTS_DIR_HIDDEN); + bool visibleExists = Storage.exists(FONTS_DIR_VISIBLE); + if (hiddenExists) return FONTS_DIR_HIDDEN; + if (visibleExists) return FONTS_DIR_VISIBLE; + return FONTS_DIR_HIDDEN; +} + +const SdCardFontFamilyInfo* SdCardFontRegistry::findFamily(const std::string& name) const { + for (const auto& f : families_) { + if (f.name == name) return &f; + } + return nullptr; +} + +int SdCardFontRegistry::getFamilyIndex(const std::string& name) const { + for (int i = 0; i < static_cast(families_.size()); i++) { + if (families_[i].name == name) return i; + } + return -1; +} diff --git a/lib/EpdFont/SdCardFontRegistry.h b/lib/EpdFont/SdCardFontRegistry.h new file mode 100644 index 00000000..f96035ed --- /dev/null +++ b/lib/EpdFont/SdCardFontRegistry.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include +#include + +struct SdCardFontFileInfo { + std::string path; // v4 on-disk naming: "///_.cpfont" + // where is "/.fonts" (preferred, hidden) or "/fonts" (visible). + // e.g. "/.fonts/NotoSansCJK/NotoSansCJK_14.cpfont" + uint8_t pointSize; // parsed from filename: 14 + uint8_t style; // always 0 in v4 (all 4 styles bundled in one file); + // kept for potential future formats +}; + +struct SdCardFontFamilyInfo { + std::string name; // directory name, e.g. "NotoSansCJK" + std::vector files; + + const SdCardFontFileInfo* findFile(uint8_t size, uint8_t style = 0) const; + bool hasSize(uint8_t size) const; + std::vector availableSizes() const; +}; + +class SdCardFontRegistry { + public: + static constexpr int MAX_SD_FAMILIES = 128; + // Two top-level roots are scanned at discovery time. Hidden is preferred + // when creating new installs; both are read from if present. + static constexpr const char* FONTS_DIR_HIDDEN = "/.fonts"; + static constexpr const char* FONTS_DIR_VISIBLE = "/fonts"; + + // Returns the existing root for `familyName` (the one that contains + // ///), or nullptr if the family is not installed in + // either root. Used by writers to keep re-installs in their existing dir. + static const char* findFamilyRoot(const char* familyName); + + // Returns the root path that should be used when creating a brand-new + // family on disk (no prior install): the existing root if exactly one of + // the two roots exists, otherwise the hidden root. + static const char* defaultWriteRoot(); + + // Scan SD card, populate families_. Returns true if any families found. + bool discover(); + + const std::vector& getFamilies() const { return families_; } + const SdCardFontFamilyInfo* findFamily(const std::string& name) const; + int getFamilyIndex(const std::string& name) const; + int getFamilyCount() const { return static_cast(families_.size()); } + + private: + std::vector families_; // sorted alphabetically + + static bool parseFilename(const char* filename, uint8_t& size, uint8_t& style); + static void scanDirectory(const char* dirPath, SdCardFontFamilyInfo& family); + // Scan one root (e.g. "/.fonts"), append families to `out`, dedup by name. + static void scanRoot(const char* rootPath, std::vector& out); +}; diff --git a/lib/EpdFont/builtinFonts/all.h b/lib/EpdFont/builtinFonts/all.h index b4b67790..2c421165 100644 --- a/lib/EpdFont/builtinFonts/all.h +++ b/lib/EpdFont/builtinFonts/all.h @@ -11,6 +11,7 @@ // OMIT_TEENSY_FONT - excludes 8px (Teensy) reading fonts; used by env:xlarge // OMIT_TINY_FONT - excludes 10px (Tiny) reading fonts; used by env:xlarge // OMIT_SMALL_FONT - excludes 12px (Small) reading fonts +// OMIT_MEDIUM_FONT - excludes 14px (Medium) reading fonts // OMIT_XLARGE_FONT - excludes 18px (Extra Large) reading fonts; used by env:tiny // OMIT_HUGE_FONT - excludes 20px (Huge) reading fonts; used by all firmware envs except env:xlarge #ifdef OMIT_EMOJI_FONTS @@ -39,10 +40,12 @@ #include BUILTIN_READING_FONT_HEADER(bitter_12_italic) #include BUILTIN_READING_FONT_HEADER(bitter_12_regular) #endif +#ifndef OMIT_MEDIUM_FONT #include BUILTIN_READING_FONT_HEADER(bitter_14_bold) #include BUILTIN_READING_FONT_HEADER(bitter_14_bolditalic) #include BUILTIN_READING_FONT_HEADER(bitter_14_italic) #include BUILTIN_READING_FONT_HEADER(bitter_14_regular) +#endif #include BUILTIN_READING_FONT_HEADER(bitter_16_bold) #include BUILTIN_READING_FONT_HEADER(bitter_16_bolditalic) #include BUILTIN_READING_FONT_HEADER(bitter_16_italic) @@ -78,10 +81,12 @@ #include BUILTIN_READING_FONT_HEADER(charein_12_italic) #include BUILTIN_READING_FONT_HEADER(charein_12_regular) #endif +#ifndef OMIT_MEDIUM_FONT #include BUILTIN_READING_FONT_HEADER(charein_14_bold) #include BUILTIN_READING_FONT_HEADER(charein_14_bolditalic) #include BUILTIN_READING_FONT_HEADER(charein_14_italic) #include BUILTIN_READING_FONT_HEADER(charein_14_regular) +#endif #include BUILTIN_READING_FONT_HEADER(charein_16_bold) #include BUILTIN_READING_FONT_HEADER(charein_16_bolditalic) #include BUILTIN_READING_FONT_HEADER(charein_16_italic) @@ -117,10 +122,12 @@ #include BUILTIN_READING_FONT_HEADER(lexenddeca_12_italic) #include BUILTIN_READING_FONT_HEADER(lexenddeca_12_regular) #endif +#ifndef OMIT_MEDIUM_FONT #include BUILTIN_READING_FONT_HEADER(lexenddeca_14_bold) #include BUILTIN_READING_FONT_HEADER(lexenddeca_14_bolditalic) #include BUILTIN_READING_FONT_HEADER(lexenddeca_14_italic) #include BUILTIN_READING_FONT_HEADER(lexenddeca_14_regular) +#endif #include BUILTIN_READING_FONT_HEADER(lexenddeca_16_bold) #include BUILTIN_READING_FONT_HEADER(lexenddeca_16_bolditalic) #include BUILTIN_READING_FONT_HEADER(lexenddeca_16_italic) diff --git a/lib/EpdFont/builtinFonts/source/.gitignore b/lib/EpdFont/builtinFonts/source/.gitignore new file mode 100644 index 00000000..1c3f6a00 --- /dev/null +++ b/lib/EpdFont/builtinFonts/source/.gitignore @@ -0,0 +1,12 @@ +# Ignore all font directories except those committed to the repo. +# Fonts like NotoSansCJK are downloaded on demand by build-sd-fonts.py. +* +!.gitignore +!NotoSerif/ +!NotoSerif/** +!NotoSans/ +!NotoSans/** +!OpenDyslexic/ +!OpenDyslexic/** +!Ubuntu/ +!Ubuntu/** diff --git a/lib/EpdFont/builtinFonts/source/Atkinson_Hyperlegible/AtkinsonHyperlegible-Bold.ttf b/lib/EpdFont/builtinFonts/source/Atkinson_Hyperlegible/AtkinsonHyperlegible-Bold.ttf deleted file mode 100644 index 80da7d0a..00000000 Binary files a/lib/EpdFont/builtinFonts/source/Atkinson_Hyperlegible/AtkinsonHyperlegible-Bold.ttf and /dev/null differ diff --git a/lib/EpdFont/builtinFonts/source/Atkinson_Hyperlegible/AtkinsonHyperlegible-BoldItalic.ttf b/lib/EpdFont/builtinFonts/source/Atkinson_Hyperlegible/AtkinsonHyperlegible-BoldItalic.ttf deleted file mode 100644 index d39bfaa6..00000000 Binary files a/lib/EpdFont/builtinFonts/source/Atkinson_Hyperlegible/AtkinsonHyperlegible-BoldItalic.ttf and /dev/null differ diff --git a/lib/EpdFont/builtinFonts/source/Atkinson_Hyperlegible/AtkinsonHyperlegible-Italic.ttf b/lib/EpdFont/builtinFonts/source/Atkinson_Hyperlegible/AtkinsonHyperlegible-Italic.ttf deleted file mode 100644 index 35f4fb75..00000000 Binary files a/lib/EpdFont/builtinFonts/source/Atkinson_Hyperlegible/AtkinsonHyperlegible-Italic.ttf and /dev/null differ diff --git a/lib/EpdFont/builtinFonts/source/Atkinson_Hyperlegible/AtkinsonHyperlegible-Regular.ttf b/lib/EpdFont/builtinFonts/source/Atkinson_Hyperlegible/AtkinsonHyperlegible-Regular.ttf deleted file mode 100644 index f0edd190..00000000 Binary files a/lib/EpdFont/builtinFonts/source/Atkinson_Hyperlegible/AtkinsonHyperlegible-Regular.ttf and /dev/null differ diff --git a/lib/EpdFont/builtinFonts/source/DMSans/DMSans-Bold.ttf b/lib/EpdFont/builtinFonts/source/DMSans/DMSans-Bold.ttf deleted file mode 100644 index 0b487c83..00000000 Binary files a/lib/EpdFont/builtinFonts/source/DMSans/DMSans-Bold.ttf and /dev/null differ diff --git a/lib/EpdFont/builtinFonts/source/DMSans/DMSans-BoldItalic.ttf b/lib/EpdFont/builtinFonts/source/DMSans/DMSans-BoldItalic.ttf deleted file mode 100644 index fb662710..00000000 Binary files a/lib/EpdFont/builtinFonts/source/DMSans/DMSans-BoldItalic.ttf and /dev/null differ diff --git a/lib/EpdFont/builtinFonts/source/DMSans/DMSans-Italic.ttf b/lib/EpdFont/builtinFonts/source/DMSans/DMSans-Italic.ttf deleted file mode 100644 index a1634134..00000000 Binary files a/lib/EpdFont/builtinFonts/source/DMSans/DMSans-Italic.ttf and /dev/null differ diff --git a/lib/EpdFont/builtinFonts/source/DMSans/DMSans-Regular.ttf b/lib/EpdFont/builtinFonts/source/DMSans/DMSans-Regular.ttf deleted file mode 100644 index e6af3717..00000000 Binary files a/lib/EpdFont/builtinFonts/source/DMSans/DMSans-Regular.ttf and /dev/null differ diff --git a/lib/EpdFont/builtinFonts/source/LiterataBook/LiterataBook-Bold.otf b/lib/EpdFont/builtinFonts/source/LiterataBook/LiterataBook-Bold.otf deleted file mode 100644 index 4c12e87c..00000000 Binary files a/lib/EpdFont/builtinFonts/source/LiterataBook/LiterataBook-Bold.otf and /dev/null differ diff --git a/lib/EpdFont/builtinFonts/source/LiterataBook/LiterataBook-BoldItalic.otf b/lib/EpdFont/builtinFonts/source/LiterataBook/LiterataBook-BoldItalic.otf deleted file mode 100644 index 12a2dc33..00000000 Binary files a/lib/EpdFont/builtinFonts/source/LiterataBook/LiterataBook-BoldItalic.otf and /dev/null differ diff --git a/lib/EpdFont/builtinFonts/source/LiterataBook/LiterataBook-Italic.otf b/lib/EpdFont/builtinFonts/source/LiterataBook/LiterataBook-Italic.otf deleted file mode 100644 index f6d8080b..00000000 Binary files a/lib/EpdFont/builtinFonts/source/LiterataBook/LiterataBook-Italic.otf and /dev/null differ diff --git a/lib/EpdFont/builtinFonts/source/LiterataBook/LiterataBook-Medium.otf b/lib/EpdFont/builtinFonts/source/LiterataBook/LiterataBook-Medium.otf deleted file mode 100644 index 5b1febaa..00000000 Binary files a/lib/EpdFont/builtinFonts/source/LiterataBook/LiterataBook-Medium.otf and /dev/null differ diff --git a/lib/EpdFont/builtinFonts/source/LiterataBook/LiterataBook-MediumItalic.otf b/lib/EpdFont/builtinFonts/source/LiterataBook/LiterataBook-MediumItalic.otf deleted file mode 100644 index 96468b60..00000000 Binary files a/lib/EpdFont/builtinFonts/source/LiterataBook/LiterataBook-MediumItalic.otf and /dev/null differ diff --git a/lib/EpdFont/builtinFonts/source/LiterataBook/LiterataBook-Regular.otf b/lib/EpdFont/builtinFonts/source/LiterataBook/LiterataBook-Regular.otf deleted file mode 100644 index 6c9ea4a6..00000000 Binary files a/lib/EpdFont/builtinFonts/source/LiterataBook/LiterataBook-Regular.otf and /dev/null differ diff --git a/lib/EpdFont/builtinFonts/source/LiterataBook/LiterataBook-Semibold.otf b/lib/EpdFont/builtinFonts/source/LiterataBook/LiterataBook-Semibold.otf deleted file mode 100644 index f8adff11..00000000 Binary files a/lib/EpdFont/builtinFonts/source/LiterataBook/LiterataBook-Semibold.otf and /dev/null differ diff --git a/lib/EpdFont/builtinFonts/source/LiterataBook/LiterataBook-SemiboldItalic.otf b/lib/EpdFont/builtinFonts/source/LiterataBook/LiterataBook-SemiboldItalic.otf deleted file mode 100644 index 301b0225..00000000 Binary files a/lib/EpdFont/builtinFonts/source/LiterataBook/LiterataBook-SemiboldItalic.otf and /dev/null differ diff --git a/lib/EpdFont/builtinFonts/source/LiterataBook/SIL Open Font License.txt b/lib/EpdFont/builtinFonts/source/LiterataBook/SIL Open Font License.txt deleted file mode 100644 index cb8783ee..00000000 --- a/lib/EpdFont/builtinFonts/source/LiterataBook/SIL Open Font License.txt +++ /dev/null @@ -1,43 +0,0 @@ -Copyright 2017 The Literata Project Authors (https://github.com/googlefonts/literata) - -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. - -The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the copyright statement(s). - -"Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. - -"Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. - -5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. \ No newline at end of file diff --git a/lib/EpdFont/builtinFonts/source/NotoSerif/NotoSerif-Bold.ttf b/lib/EpdFont/builtinFonts/source/NotoSerif/NotoSerif-Bold.ttf deleted file mode 100644 index 519d3135..00000000 Binary files a/lib/EpdFont/builtinFonts/source/NotoSerif/NotoSerif-Bold.ttf and /dev/null differ diff --git a/lib/EpdFont/builtinFonts/source/NotoSerif/NotoSerif-BoldItalic.ttf b/lib/EpdFont/builtinFonts/source/NotoSerif/NotoSerif-BoldItalic.ttf deleted file mode 100644 index 23ea70aa..00000000 Binary files a/lib/EpdFont/builtinFonts/source/NotoSerif/NotoSerif-BoldItalic.ttf and /dev/null differ diff --git a/lib/EpdFont/builtinFonts/source/NotoSerif/NotoSerif-Italic.ttf b/lib/EpdFont/builtinFonts/source/NotoSerif/NotoSerif-Italic.ttf deleted file mode 100644 index c3ccb5fe..00000000 Binary files a/lib/EpdFont/builtinFonts/source/NotoSerif/NotoSerif-Italic.ttf and /dev/null differ diff --git a/lib/EpdFont/builtinFonts/source/NotoSerif/NotoSerif-Regular.ttf b/lib/EpdFont/builtinFonts/source/NotoSerif/NotoSerif-Regular.ttf deleted file mode 100644 index 1b13cd72..00000000 Binary files a/lib/EpdFont/builtinFonts/source/NotoSerif/NotoSerif-Regular.ttf and /dev/null differ diff --git a/lib/EpdFont/builtinFonts/source/NotoSerif/OFL.txt b/lib/EpdFont/builtinFonts/source/NotoSerif/OFL.txt deleted file mode 100644 index 09f020bb..00000000 --- a/lib/EpdFont/builtinFonts/source/NotoSerif/OFL.txt +++ /dev/null @@ -1,93 +0,0 @@ -Copyright 2022 The Noto Project Authors (https://github.com/notofonts/latin-greek-cyrillic) - -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: -https://openfontlicense.org - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/lib/EpdFont/builtinFonts/source/OpenDyslexic/OFL.txt b/lib/EpdFont/builtinFonts/source/OpenDyslexic/OFL.txt deleted file mode 100644 index 0a1d034b..00000000 --- a/lib/EpdFont/builtinFonts/source/OpenDyslexic/OFL.txt +++ /dev/null @@ -1,94 +0,0 @@ -Copyright (c) 2019-07-29, Abbie Gonzalez (https://abbiecod.es|support@abbiecod.es), -with Reserved Font Name OpenDyslexic. -Copyright (c) 12/2012 - 2019 -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: -http://scripts.sil.org/OFL - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/lib/EpdFont/builtinFonts/source/OpenDyslexic/OpenDyslexic-Bold.otf b/lib/EpdFont/builtinFonts/source/OpenDyslexic/OpenDyslexic-Bold.otf deleted file mode 100644 index fc14a5ac..00000000 Binary files a/lib/EpdFont/builtinFonts/source/OpenDyslexic/OpenDyslexic-Bold.otf and /dev/null differ diff --git a/lib/EpdFont/builtinFonts/source/OpenDyslexic/OpenDyslexic-BoldItalic.otf b/lib/EpdFont/builtinFonts/source/OpenDyslexic/OpenDyslexic-BoldItalic.otf deleted file mode 100644 index 99b4b1d1..00000000 Binary files a/lib/EpdFont/builtinFonts/source/OpenDyslexic/OpenDyslexic-BoldItalic.otf and /dev/null differ diff --git a/lib/EpdFont/builtinFonts/source/OpenDyslexic/OpenDyslexic-Italic.otf b/lib/EpdFont/builtinFonts/source/OpenDyslexic/OpenDyslexic-Italic.otf deleted file mode 100644 index 8f53b290..00000000 Binary files a/lib/EpdFont/builtinFonts/source/OpenDyslexic/OpenDyslexic-Italic.otf and /dev/null differ diff --git a/lib/EpdFont/builtinFonts/source/OpenDyslexic/OpenDyslexic-Regular.otf b/lib/EpdFont/builtinFonts/source/OpenDyslexic/OpenDyslexic-Regular.otf deleted file mode 100644 index 039cedaf..00000000 Binary files a/lib/EpdFont/builtinFonts/source/OpenDyslexic/OpenDyslexic-Regular.otf and /dev/null differ diff --git a/lib/EpdFont/builtinFonts/source/Ubuntu/UFL.txt b/lib/EpdFont/builtinFonts/source/Ubuntu/UFL.txt deleted file mode 100644 index 6e722c88..00000000 --- a/lib/EpdFont/builtinFonts/source/Ubuntu/UFL.txt +++ /dev/null @@ -1,96 +0,0 @@ -------------------------------- -UBUNTU FONT LICENCE Version 1.0 -------------------------------- - -PREAMBLE -This licence allows the licensed fonts to be used, studied, modified and -redistributed freely. The fonts, including any derivative works, can be -bundled, embedded, and redistributed provided the terms of this licence -are met. The fonts and derivatives, however, cannot be released under -any other licence. The requirement for fonts to remain under this -licence does not require any document created using the fonts or their -derivatives to be published under this licence, as long as the primary -purpose of the document is not to be a vehicle for the distribution of -the fonts. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this licence and clearly marked as such. This may -include source files, build scripts and documentation. - -"Original Version" refers to the collection of Font Software components -as received under this licence. - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to -a new environment. - -"Copyright Holder(s)" refers to all individuals and companies who have a -copyright ownership of the Font Software. - -"Substantially Changed" refers to Modified Versions which can be easily -identified as dissimilar to the Font Software by users of the Font -Software comparing the Original Version with the Modified Version. - -To "Propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification and with or without charging -a redistribution fee), making available to the public, and in some -countries other activities as well. - -PERMISSION & CONDITIONS -This licence does not grant any rights under trademark law and all such -rights are reserved. - -Permission is hereby granted, free of charge, to any person obtaining a -copy of the Font Software, to propagate the Font Software, subject to -the below conditions: - -1) Each copy of the Font Software must contain the above copyright -notice and this licence. These can be included either as stand-alone -text files, human-readable headers or in the appropriate machine- -readable metadata fields within text or binary files as long as those -fields can be easily viewed by the user. - -2) The font name complies with the following: -(a) The Original Version must retain its name, unmodified. -(b) Modified Versions which are Substantially Changed must be renamed to -avoid use of the name of the Original Version or similar names entirely. -(c) Modified Versions which are not Substantially Changed must be -renamed to both (i) retain the name of the Original Version and (ii) add -additional naming elements to distinguish the Modified Version from the -Original Version. The name of such Modified Versions must be the name of -the Original Version, with "derivative X" where X represents the name of -the new work, appended to that name. - -3) The name(s) of the Copyright Holder(s) and any contributor to the -Font Software shall not be used to promote, endorse or advertise any -Modified Version, except (i) as required by this licence, (ii) to -acknowledge the contribution(s) of the Copyright Holder(s) or (iii) with -their explicit written permission. - -4) The Font Software, modified or unmodified, in part or in whole, must -be distributed entirely under this licence, and must not be distributed -under any other licence. The requirement for fonts to remain under this -licence does not affect any document created using the Font Software, -except any version of the Font Software extracted from a document -created using the Font Software may only be distributed under this -licence. - -TERMINATION -This licence becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF -COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER -DEALINGS IN THE FONT SOFTWARE. diff --git a/lib/EpdFont/builtinFonts/source/Ubuntu/Ubuntu-Bold.ttf b/lib/EpdFont/builtinFonts/source/Ubuntu/Ubuntu-Bold.ttf deleted file mode 100644 index c2293d5c..00000000 Binary files a/lib/EpdFont/builtinFonts/source/Ubuntu/Ubuntu-Bold.ttf and /dev/null differ diff --git a/lib/EpdFont/builtinFonts/source/Ubuntu/Ubuntu-Regular.ttf b/lib/EpdFont/builtinFonts/source/Ubuntu/Ubuntu-Regular.ttf deleted file mode 100644 index b5505e67..00000000 Binary files a/lib/EpdFont/builtinFonts/source/Ubuntu/Ubuntu-Regular.ttf and /dev/null differ diff --git a/lib/EpdFont/scripts/build-font-ids.sh b/lib/EpdFont/scripts/build-font-ids.sh index 78995cca..291ae1b9 100755 --- a/lib/EpdFont/scripts/build-font-ids.sh +++ b/lib/EpdFont/scripts/build-font-ids.sh @@ -97,6 +97,22 @@ reading_font_id BITTER_20_FONT_ID \ ./bitter_20_regular.h ./bitter_20_bold.h ./bitter_20_bolditalic.h ./bitter_20_italic.h # UI fonts — no emoji variant -echo "#define UI_10_FONT_ID ($(hash_files ./ubuntu_10_regular.h ./ubuntu_10_bold.h))" -echo "#define UI_12_FONT_ID ($(hash_files ./ubuntu_12_regular.h ./ubuntu_12_bold.h))" +echo "#define UI_10_FONT_ID ($(hash_files ./inter_10_regular.h ./inter_10_bold.h))" +echo "#define UI_12_FONT_ID ($(hash_files ./inter_12_regular.h ./inter_12_bold.h))" echo "#define SMALL_FONT_ID ($(hash_files ./inter_8_regular.h))" + +echo "" +echo "// Font ID 0 is reserved as the \"not found\" sentinel." +echo "// Guard against any hash accidentally producing 0." +for id in \ + NOTOEMOJI_10_FONT_ID NOTOEMOJI_12_FONT_ID NOTOEMOJI_14_FONT_ID NOTOEMOJI_16_FONT_ID \ + NOTOSYMBOLS_10_FONT_ID NOTOSYMBOLS_12_FONT_ID NOTOSYMBOLS_14_FONT_ID NOTOSYMBOLS_16_FONT_ID \ + LEXENDDECA_8_FONT_ID LEXENDDECA_10_FONT_ID LEXENDDECA_12_FONT_ID LEXENDDECA_14_FONT_ID \ + LEXENDDECA_16_FONT_ID LEXENDDECA_18_FONT_ID LEXENDDECA_20_FONT_ID \ + CHAREINK_8_FONT_ID CHAREINK_10_FONT_ID CHAREINK_12_FONT_ID CHAREINK_14_FONT_ID \ + CHAREINK_16_FONT_ID CHAREINK_18_FONT_ID CHAREINK_20_FONT_ID \ + BITTER_8_FONT_ID BITTER_10_FONT_ID BITTER_12_FONT_ID BITTER_14_FONT_ID \ + BITTER_16_FONT_ID BITTER_18_FONT_ID BITTER_20_FONT_ID \ + UI_10_FONT_ID UI_12_FONT_ID SMALL_FONT_ID; do + echo "static_assert(${id} != 0, \"Font ID collision with sentinel\");" +done diff --git a/lib/EpdFont/scripts/build-sd-fonts.py b/lib/EpdFont/scripts/build-sd-fonts.py new file mode 100755 index 00000000..8f981fbe --- /dev/null +++ b/lib/EpdFont/scripts/build-sd-fonts.py @@ -0,0 +1,406 @@ +#!/usr/bin/env python3 +"""Build SD card fonts from a declarative YAML config. + +Reads sd-fonts.yaml, downloads any missing source fonts, runs +fontconvert_sdcard.py in parallel for each family, and optionally +generates the fonts.json manifest. + +Usage: + # Generate fonts (output in ./output/) + python3 build-sd-fonts.py + + # Generate fonts + manifest + python3 build-sd-fonts.py --manifest --base-url "http://localhost:8000/" + + # Custom config / output paths + python3 build-sd-fonts.py --config my-fonts.yaml --output-dir dist/ + + # Generate only specific families + python3 build-sd-fonts.py --only Literata,IBMPlexMono + + # Stream child process output for debugging + python3 build-sd-fonts.py --verbose + + # Override the per-family timeout (default: 600s) + python3 build-sd-fonts.py --timeout 1200 +""" + +import argparse +import os +import shutil +import subprocess +import sys +import tempfile +import threading +import time +import urllib.request +from concurrent.futures import ProcessPoolExecutor, as_completed +from pathlib import Path + +import yaml + +SCRIPT_DIR = Path(__file__).parent +FONTCONVERT = SCRIPT_DIR / "fontconvert_sdcard.py" +EPDFONTS_DIR = SCRIPT_DIR.parent # lib/EpdFont +DEFAULT_CONFIG = SCRIPT_DIR / "sd-fonts.yaml" +DEFAULT_OUTPUT = SCRIPT_DIR / "output" +DOWNLOAD_DIR = SCRIPT_DIR / "downloaded_fonts" +INSTANCE_DIR = SCRIPT_DIR / "instanced_fonts" + + +def download_font(url: str, dest: Path) -> Path: + """Download a font file if not already cached. Returns the local path.""" + if dest.exists(): + return dest + dest.parent.mkdir(parents=True, exist_ok=True) + print(f" Downloading {dest.name}...") + try: + urllib.request.urlretrieve(url, dest) + except Exception as e: + dest.unlink(missing_ok=True) + raise RuntimeError(f"Failed to download {url}: {e}") from e + size_kb = dest.stat().st_size / 1024 + print(f" Downloaded {dest.name} ({size_kb:.0f} KB)") + return dest + + +def extract_static_instance(source_path: Path, axes: dict, family_name: str, style_name: str) -> Path: + """Use fonttools instancer to pin variable font axes, producing a static TTF. + + Caches the result in INSTANCE_DIR// + + +

    CrossInk

    + + + +
    +

    Installed Fonts

    +

    Loading...

    +
    + +
    +

    Upload Font

    +
    + + +
    +

    +
    +
    + +

    CrossInk • Open Source

    + + + + diff --git a/src/network/html/HomePage.html b/src/network/html/HomePage.html index 96c3d959..fff2b68f 100644 --- a/src/network/html/HomePage.html +++ b/src/network/html/HomePage.html @@ -111,6 +111,7 @@

    CrossInk

    Home File Manager Settings + Fonts
    diff --git a/src/network/html/SettingsPage.html b/src/network/html/SettingsPage.html index ef7edb07..7c2a36d9 100644 --- a/src/network/html/SettingsPage.html +++ b/src/network/html/SettingsPage.html @@ -292,6 +292,7 @@

    CrossInk

    Home File Manager Settings + Fonts
    diff --git a/src/simulator/SimulatorSmokeTest.cpp b/src/simulator/SimulatorSmokeTest.cpp new file mode 100644 index 00000000..2af15fb7 --- /dev/null +++ b/src/simulator/SimulatorSmokeTest.cpp @@ -0,0 +1,282 @@ +#ifdef SIMULATOR + +#include "SimulatorSmokeTest.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include "CrossPointSettings.h" +#include "MappedInputManager.h" +#include "activities/ActivityManager.h" +#include "activities/reader/EpubReaderMenuActivity.h" +#include "activities/reader/ReaderOptionsActivity.h" +#include "components/UITheme.h" + +extern ActivityManager activityManager; +extern GfxRenderer renderer; +extern MappedInputManager mappedInputManager; + +namespace { + +enum class SmokeStep : uint8_t { + Start, + Home, + FileBrowser, + RecentBooks, + Settings, + ReaderOptions, + ReaderMenu, + Sleep, + Reader, + ReaderInput, + Done, +}; + +class SimulatorSmokeTest { + public: + void tick() { + if (!enabled()) return; + + try { + tickImpl(); + } catch (const std::exception& e) { + fail("Unhandled exception: %s", e.what()); + } catch (...) { + fail("Unhandled non-standard exception"); + } + } + + private: + enum class ScriptActionType : uint8_t { Press, Release, Render }; + + struct ScriptAction { + ScriptActionType type; + MappedInputManager::Button button; + const char* label; + int settleFrames; + }; + + SmokeStep step = SmokeStep::Start; + int settleFrames = 0; + const char* activeStepName = nullptr; + std::vector inputScript; + size_t scriptIndex = 0; + + static bool enabled() { return std::getenv("CROSSINK_SIMULATOR_SMOKE_TEST") != nullptr; } + + static int pageTurnCount() { + const char* raw = std::getenv("CROSSINK_SIMULATOR_SMOKE_PAGE_TURNS"); + if (raw == nullptr || raw[0] == '\0') { + return 2; + } + return std::max(0, std::atoi(raw)); + } + + static void applyRequestedTheme() { + const char* raw = std::getenv("CROSSINK_SIMULATOR_SMOKE_THEME"); + if (raw == nullptr || raw[0] == '\0') { + return; + } + + const int theme = std::atoi(raw); + if (theme < 0 || theme >= CrossPointSettings::UI_THEME_COUNT) { + fail("Invalid smoke test theme index: %d", theme); + } + + SETTINGS.uiTheme = static_cast(theme); + UITheme::getInstance().reload(); + LOG_INF("SMOKE", "Using theme index %d", theme); + } + + [[noreturn]] static void fail(const char* message) { + LOG_ERR("SMOKE", "%s", message); + std::_Exit(2); + } + + template + [[noreturn]] static void fail(const char* format, Args... args) { + logPrintf("ERR", "SMOKE", format, args...); + logPrintf("ERR", "SMOKE", "\n"); + std::_Exit(2); + } + + static void renderCurrentStep(const char* name) { + LOG_INF("SMOKE", "Rendering %s", name); + if (activityManager.requestUpdateAndWait() != RequestUpdateResult::Rendered) { + fail("Render was rejected for %s", name); + } + } + + void queueStep(const char* name, SmokeStep nextStep, int framesToSettle = 3) { + activeStepName = name; + settleFrames = framesToSettle; + step = nextStep; + } + + void tickImpl() { + mappedInputManager.simulatorClearInputFrame(); + + if (settleFrames > 0) { + --settleFrames; + if (settleFrames == 0 && activeStepName != nullptr) { + renderCurrentStep(activeStepName); + activeStepName = nullptr; + } + return; + } + + switch (step) { + case SmokeStep::Start: + LOG_INF("SMOKE", "Starting simulator smoke test"); + if (!CrossPointSettings::verifySleepTimeoutMigrationContract()) { + fail("Sleep timeout migration contract failed"); + } + applyRequestedTheme(); + activityManager.goHome(); + queueStep("Home", SmokeStep::Home); + break; + + case SmokeStep::Home: + activityManager.goToFileBrowser("/books"); + queueStep("File Browser", SmokeStep::FileBrowser); + break; + + case SmokeStep::FileBrowser: + activityManager.goToRecentBooks(); + queueStep("Recent Books", SmokeStep::RecentBooks); + break; + + case SmokeStep::RecentBooks: + activityManager.goToSettings(); + queueStep("Settings", SmokeStep::Settings); + break; + + case SmokeStep::Settings: + activityManager.replaceActivity(std::make_unique(renderer, mappedInputManager)); + queueStep("Reader Options", SmokeStep::ReaderOptions); + break; + + case SmokeStep::ReaderOptions: + activityManager.replaceActivity(std::make_unique( + renderer, mappedInputManager, "Smoke Test", 1, 1, 0, SETTINGS.orientation, false, false, false, false)); + queueStep("Reader Menu", SmokeStep::ReaderMenu); + break; + + case SmokeStep::ReaderMenu: + activityManager.goToSleep(); + queueStep("Sleep", SmokeStep::Sleep); + break; + + case SmokeStep::Sleep: { + const char* bookPath = std::getenv("CROSSINK_SIMULATOR_SMOKE_BOOK"); + if (bookPath == nullptr || bookPath[0] == '\0') { + LOG_INF("SMOKE", "Skipping Reader step; CROSSINK_SIMULATOR_SMOKE_BOOK is not set"); + step = SmokeStep::Reader; + break; + } + if (!Storage.exists(bookPath)) { + fail("Smoke test book is missing: %s", bookPath); + } + activityManager.goToReader(bookPath, true); + queueStep("Reader", SmokeStep::Reader, 8); + break; + } + + case SmokeStep::Reader: + buildReaderInputScript(); + step = SmokeStep::ReaderInput; + break; + + case SmokeStep::ReaderInput: + runReaderInputScript(); + break; + + case SmokeStep::Done: + LOG_INF("SMOKE", "Simulator smoke test passed"); + std::_Exit(0); + } + } + + static ScriptAction press(MappedInputManager::Button button) { return {ScriptActionType::Press, button, nullptr, 0}; } + + static ScriptAction release(MappedInputManager::Button button) { + return {ScriptActionType::Release, button, nullptr, 0}; + } + + static ScriptAction render(const char* label, int framesToSettle = 3) { + return {ScriptActionType::Render, MappedInputManager::Button::Back, label, framesToSettle}; + } + + void addTap(MappedInputManager::Button button) { + inputScript.push_back(press(button)); + inputScript.push_back(release(button)); + } + + void buildReaderInputScript() { + inputScript.clear(); + scriptIndex = 0; + + const int turns = pageTurnCount(); + for (int i = 0; i < turns; i++) { + addTap(MappedInputManager::Button::PageForward); + inputScript.push_back(render("Reader after page forward", 4)); + } + + addTap(MappedInputManager::Button::Confirm); + inputScript.push_back(render("Reader Menu opened from EPUB", 4)); + + addTap(MappedInputManager::Button::Down); + inputScript.push_back(render("Reader Menu Reader Options selection", 3)); + + addTap(MappedInputManager::Button::Confirm); + inputScript.push_back(render("Reader Options opened from Reader Menu", 4)); + + addTap(MappedInputManager::Button::Down); + inputScript.push_back(render("Reader Options after navigation", 3)); + + addTap(MappedInputManager::Button::Confirm); + inputScript.push_back(render("Reader Options after toggle", 3)); + + addTap(MappedInputManager::Button::Back); + inputScript.push_back(render("Reader Menu after closing Reader Options", 4)); + + addTap(MappedInputManager::Button::Back); + inputScript.push_back(render("Reader after closing Reader Menu", 4)); + + LOG_INF("SMOKE", "Running reader input script with %d page turn(s)", turns); + } + + void runReaderInputScript() { + if (scriptIndex >= inputScript.size()) { + step = SmokeStep::Done; + return; + } + + const auto& action = inputScript[scriptIndex++]; + switch (action.type) { + case ScriptActionType::Press: + mappedInputManager.simulatorInjectPress(action.button); + break; + case ScriptActionType::Release: + mappedInputManager.simulatorInjectRelease(action.button); + break; + case ScriptActionType::Render: + queueStep(action.label, SmokeStep::ReaderInput, action.settleFrames); + break; + } + } +}; + +SimulatorSmokeTest smokeTest; + +} // namespace + +void runSimulatorSmokeTestTick() { smokeTest.tick(); } + +#endif diff --git a/src/simulator/SimulatorSmokeTest.h b/src/simulator/SimulatorSmokeTest.h new file mode 100644 index 00000000..a29fa74a --- /dev/null +++ b/src/simulator/SimulatorSmokeTest.h @@ -0,0 +1,5 @@ +#pragma once + +#ifdef SIMULATOR +void runSimulatorSmokeTestTick(); +#endif diff --git a/src/util/StringUtils.cpp b/src/util/StringUtils.cpp index 81be07ee..1e572915 100644 --- a/src/util/StringUtils.cpp +++ b/src/util/StringUtils.cpp @@ -2,9 +2,53 @@ #include -namespace StringUtils { +#include +#include -std::string sanitizeFilename(const std::string& name, size_t maxBytes) { +namespace { + +constexpr size_t MAX_EXTENSION_BYTES = 16; + +bool isInvalidFilenameCodepoint(uint32_t cp) { + return cp == '/' || cp == '\\' || cp == ':' || cp == '*' || cp == '?' || cp == '"' || cp == '<' || cp == '>' || + cp == '|'; +} + +std::string trimFilenameEdges(const std::string& name) { + size_t start = 0; + while (start < name.size() && (name[start] == ' ' || name[start] == '.')) { + start++; + } + + size_t end = name.size(); + while (end > start && (name[end - 1] == ' ' || name[end - 1] == '.')) { + end--; + } + + return name.substr(start, end - start); +} + +size_t findSafeExtensionStart(const std::string& name) { + const size_t dot = name.find_last_of('.'); + if (dot == std::string::npos || dot == 0 || dot + 1 >= name.size()) { + return std::string::npos; + } + + const size_t extensionBytes = name.size() - dot; + if (extensionBytes > MAX_EXTENSION_BYTES) { + return std::string::npos; + } + + for (size_t i = dot + 1; i < name.size(); i++) { + if (!std::isalnum(static_cast(name[i]))) { + return std::string::npos; + } + } + + return dot; +} + +std::string sanitizeFilenamePart(const std::string& name, size_t maxBytes) { std::string result; result.reserve(std::min(name.size(), maxBytes)); @@ -20,8 +64,7 @@ std::string sanitizeFilename(const std::string& name, size_t maxBytes) { const auto* cpStart = text; uint32_t cp = utf8NextCodepoint(&text); - if (cp == '/' || cp == '\\' || cp == ':' || cp == '*' || cp == '?' || cp == '"' || cp == '<' || cp == '>' || - cp == '|') { + if (isInvalidFilenameCodepoint(cp)) { // Replace illegal and control characters with '_' if (result.length() + 1 > maxBytes) break; result += '_'; @@ -43,4 +86,28 @@ std::string sanitizeFilename(const std::string& name, size_t maxBytes) { return result.empty() ? "book" : result; } +} // namespace + +namespace StringUtils { + +std::string sanitizeFilename(const std::string& name, size_t maxBytes) { + const std::string trimmedName = trimFilenameEdges(name); + const size_t extensionStart = findSafeExtensionStart(trimmedName); + if (extensionStart != std::string::npos) { + const std::string extension = trimmedName.substr(extensionStart); + if (extension.size() < maxBytes) { + const size_t baseBudget = maxBytes - extension.size(); + std::string base = sanitizeFilenamePart(trimmedName.substr(0, extensionStart), baseBudget); + if (base.size() > baseBudget) { + base.resize(baseBudget); + } + if (!base.empty()) { + return base + extension; + } + } + } + + return sanitizeFilenamePart(name, maxBytes); +} + } // namespace StringUtils diff --git a/src/util/StringUtils.h b/src/util/StringUtils.h index 1fa6cc01..d1c77c3e 100644 --- a/src/util/StringUtils.h +++ b/src/util/StringUtils.h @@ -1,14 +1,17 @@ #pragma once +#include #include namespace StringUtils { +constexpr size_t kDefaultMaxFilenameBytes = 150; + /** * Sanitize a string for use as a filename. * Replaces invalid characters with underscores, trims spaces/dots, * and limits length to maxBytes bytes. */ -std::string sanitizeFilename(const std::string& name, size_t maxBytes = 100); +std::string sanitizeFilename(const std::string& name, size_t maxBytes = kDefaultMaxFilenameBytes); } // namespace StringUtils diff --git a/test/README b/test/README index 9b1e87bc..fb3802bf 100644 --- a/test/README +++ b/test/README @@ -9,3 +9,20 @@ in the development cycle. More information about PlatformIO Unit Testing: - https://docs.platformio.org/en/latest/advanced/unit-testing/index.html + +Simulator smoke test: +- Run `./scripts/run_simulator_smoke_test.py` from the repo root. +- The script builds `env:simulator`, creates a temporary `fs_`, copies a test EPUB into it, opens common screens, renders them, drives reader page/menu input, and fails on crashes or missing success output. +- Use `--book path/to/book.epub` to choose a specific EPUB and `--page-turns 10` to change how far the reader flow navigates. +- Use `--theme lyra-carousel` to run the flow with a specific UI theme. Supported names include `classic`, `lyra`, `lyra-extended`, `roundedraff`, and `lyra-carousel`. +- Use `--no-build` to reuse the existing simulator binary or `--window` to show the SDL window instead of running headless. + +Expected coverage: +- This is a fast regression tripwire, similar to a small browser smoke test. It proves the simulator can boot, open common screens, load the selected EPUB, turn pages, open reader menus, and exit without crashing or hanging. +- It is useful for catching activity entry/render crashes, bad allocations, uncaught exceptions, assertions, segfaults, AddressSanitizer/UndefinedBehaviorSanitizer reports, obvious hangs, and EPUB/theme-specific failures for the exact book and theme under test. +- It also covers a narrow scripted reader path: page forward, open the reader menu, enter Reader Options, navigate/toggle one option, and back out to the reader. + +Limits: +- It does not replace hardware testing. The desktop simulator has different memory, filesystem, timing, display, sleep, button, battery, USB, Wi-Fi, and OTA behavior than the ESP32-C3 device. +- It should not be expected to catch exact hardware heap pressure, SD-card locking/timing issues, e-ink ghosting or refresh quality, real deep-sleep wake behavior, electrical button behavior, subtle visual layout problems, or EPUB cases that are not present in the selected test book. +- After a simulator pass, still verify important changes on hardware with the same EPUB and theme by turning pages, opening the reader menu, entering Reader Options, toggling a setting, backing out, and watching for crashes or memory-related logs. diff --git a/test/differential_rounding/DifferentialRoundingTest.cpp b/test/differential_rounding/DifferentialRoundingTest.cpp index fd454839..dc8ea115 100644 --- a/test/differential_rounding/DifferentialRoundingTest.cpp +++ b/test/differential_rounding/DifferentialRoundingTest.cpp @@ -5,6 +5,8 @@ #include "lib/EpdFont/EpdFont.h" #include "lib/EpdFont/EpdFontData.h" +#include "lib/EpdFont/EpdFontFamily.h" +#include "lib/Utf8/Utf8.h" static int testsPassed = 0; static int testsFailed = 0; @@ -32,7 +34,7 @@ static int testsFailed = 0; // ============================================================================ // Synthetic test font // -// Glyphs: 'T' (0x54), 'a' (0x61), 'o' (0x6F), 'x' (0x78) +// Glyphs: space (0x20), 'T' (0x54), 'a' (0x61), 'o' (0x6F), 'x' (0x78) // - 'x' advance is 136 FP (8.5px) -- frac = 8, exactly at the rounding // boundary where absolute vs differential snapping diverges for "oo". // - No U+FFFD replacement glyph, so unknown codepoints trigger the @@ -46,17 +48,19 @@ static int testsFailed = 0; // clang-format off static const EpdGlyph kGlyphs[] = { // idx width height advanceX left top dataLength dataOffset - /* 0 'T' */ { 8, 12, 137, 0, 12, 0, 0 }, - /* 1 'a' */ { 7, 8, 130, 0, 8, 0, 0 }, - /* 2 'o' */ { 8, 8, 145, 0, 8, 0, 0 }, - /* 3 'x' */ { 7, 8, 136, 0, 8, 0, 0 }, + /* 0 ' ' */ { 0, 0, 64, 0, 0, 0, 0 }, + /* 1 'T' */ { 8, 12, 137, 0, 12, 0, 0 }, + /* 2 'a' */ { 7, 8, 130, 0, 8, 0, 0 }, + /* 3 'o' */ { 8, 8, 145, 0, 8, 0, 0 }, + /* 4 'x' */ { 7, 8, 136, 0, 8, 0, 0 }, }; static const EpdUnicodeInterval kIntervals[] = { - { 0x54, 0x54, 0 }, // 'T' -> glyph[0] - { 0x61, 0x61, 1 }, // 'a' -> glyph[1] - { 0x6F, 0x6F, 2 }, // 'o' -> glyph[2] - { 0x78, 0x78, 3 }, // 'x' -> glyph[3] + { 0x20, 0x20, 0 }, // ' ' -> glyph[0] + { 0x54, 0x54, 1 }, // 'T' -> glyph[1] + { 0x61, 0x61, 2 }, // 'a' -> glyph[2] + { 0x6F, 0x6F, 3 }, // 'o' -> glyph[3] + { 0x78, 0x78, 4 }, // 'x' -> glyph[4] }; static const EpdKernClassEntry kKernLeft[] = { @@ -77,7 +81,7 @@ static const EpdFontData kTestFontData = { .bitmap = nullptr, .glyph = kGlyphs, .intervals = kIntervals, - .intervalCount = 4, + .intervalCount = 5, .advanceY = 16, .ascender = 12, .descender = 0, @@ -98,6 +102,7 @@ static const EpdFontData kTestFontData = { // clang-format on static EpdFont testFont(&kTestFontData); +static EpdFontFamily testFontFamily(&testFont); // Helper: return width from getTextDimensions static int textWidth(const char* str) { @@ -346,6 +351,48 @@ void testNullGlyphAdvancePreserved() { PASS(); } +void testFamilyMissingGlyphUsesReplacementFallback() { + printf("testFamilyMissingGlyphUsesReplacementFallback...\n"); + + // The low-level EpdFont intentionally has no U+FFFD here, mirroring compact UI + // fonts. The family layer should still resolve unknown characters to a visible + // synthetic replacement glyph instead of measuring them as blank/null. + ASSERT_EQ(testFontFamily.getFallbackCodepoint('Z'), REPLACEMENT_GLYPH); + + int w = 0, h = 0; + testFontFamily.getTextDimensions("Z", &w, &h); + printf(" family width(\"Z\") = %d, height = %d\n", w, h); + + ASSERT_EQ(w, 9); + ASSERT_EQ(h, 12); + + printf(" Missing family glyphs use visible replacement fallback\n"); + PASS(); +} + +void testFamilySpaceGlyphsStayBlank() { + printf("testFamilySpaceGlyphsStayBlank...\n"); + + // EPUB paragraph indentation uses U+2003 EM SPACE. If the selected reading font + // lacks that exact glyph, it should stay blank/null like upstream, not become the + // visible missing-glyph marker or an ASCII space with its own extra width. + ASSERT_EQ(testFontFamily.getFallbackCodepoint(0x2003), 0x2003); + + int w = 0, h = 0; + testFontFamily.getTextDimensions("\xE2\x80\x83", &w, &h); + printf(" family dimensions(U+2003) = %d x %d\n", w, h); + + ASSERT_EQ(w, 0); + ASSERT_EQ(h, 0); + + testFontFamily.getTextDimensions("o\xE2\x80\x83o", &w, &h); + printf(" family width(\"o U+2003 o\") = %d\n", w); + ASSERT_EQ(w, textWidth("oo")); + + printf(" Unicode space fallbacks stay blank\n"); + PASS(); +} + void testHeightCalculation() { printf("testHeightCalculation...\n"); @@ -374,6 +421,8 @@ int main() { testKnownWidths(); testPairConsistencyViaFont(); testNullGlyphAdvancePreserved(); + testFamilyMissingGlyphUsesReplacementFallback(); + testFamilySpaceGlyphsStayBlank(); testHeightCalculation(); printf("\n=== Results: %d passed, %d failed ===\n", testsPassed, testsFailed); diff --git a/test/epubs-src/test_synthetic_unicode_glyphs/OEBPS/chapter1.xhtml b/test/epubs-src/test_synthetic_unicode_glyphs/OEBPS/chapter1.xhtml new file mode 100644 index 00000000..0a02e782 --- /dev/null +++ b/test/epubs-src/test_synthetic_unicode_glyphs/OEBPS/chapter1.xhtml @@ -0,0 +1,34 @@ + + + + + Glyph Reference + + + +

    Glyph Reference

    +

    This chapter covers the synthetic Unicode glyphs added in commit c4357d06.

    +

    Each line should show a visible glyph after the label, not an empty box or missing-character mark.

    + +

    Solid Shapes

    +

    U+2588 FULL BLOCK: █ ███ █████

    +

    U+25A0 BLACK SQUARE: ■ ■■■ ■■■■■

    +

    Mixed solid run: A█B C■D █■█■█ end

    +

    Bold solid run: A█B C■D █■█■█ end

    + +

    Greek Fallbacks

    +

    U+0393 GREEK CAPITAL GAMMA: Γ ΓΓΓ Γ test

    +

    U+03B5 GREEK SMALL EPSILON: ε εεε ε test

    +

    U+03C9 GREEK SMALL OMEGA: ω ωωω ω test

    +

    Mixed Greek fallback run: Γ ε ω Γεω

    +

    Bold Greek fallback run: Γ ε ω Γεω

    + +

    Alias Glyph

    +

    U+02BB MODIFIER LETTER TURNED COMMA: ʻokina ʻ single ʻ marks

    +

    Compare with U+2018 LEFT SINGLE QUOTATION MARK: ‘okina ‘ single ‘ marks

    + +

    Inline Context

    +

    The synthetic glyphs should work inside normal prose: this sentence has a full block █, a black square ■, Greek Γεω, and turned comma ʻ in one paragraph.

    +

    They should also survive punctuation: [█] (■), Γ; ε, ω. The final word ends with a block: done█

    + + diff --git a/test/epubs-src/test_synthetic_unicode_glyphs/OEBPS/chapter2.xhtml b/test/epubs-src/test_synthetic_unicode_glyphs/OEBPS/chapter2.xhtml new file mode 100644 index 00000000..fb129cdb --- /dev/null +++ b/test/epubs-src/test_synthetic_unicode_glyphs/OEBPS/chapter2.xhtml @@ -0,0 +1,24 @@ + + + + + Block Stress Cases + + + +

    Block Stress Cases

    +

    Use this chapter to check that solid synthetic glyphs near line and page edges do not flood out-of-bounds render logs.

    + +

    Line Wrapping

    +

    Short words before blocks: alpha beta gamma ███████████████████████████████████████████████████████████████████ omega.

    +

    Blocks before text: ███████████████████████████████████████████████████████████████████ after the long block run.

    +

    Alternating solid glyphs: █■█■█■█■█■█■█■█■█■█■█■█■█■█■█■█■█■█■█■█■█■█■█■█■█■█■█■█■█■ end.

    + +

    Dense Paragraph

    +

    ████ ████ ████ ████ ████ ████ ████ ████ ████ ████ ████ ████ ████ ████ ████ ████ ████ ████ ████ ████ ████ ████ ████ ████ ████ ████ ████ ████ ████ ████

    + +

    Mixed Fallbacks At Wrap Points

    +

    Γεω Γεω Γεω Γεω Γεω Γεω Γεω Γεω Γεω Γεω Γεω Γεω Γεω Γεω Γεω Γεω Γεω Γεω Γεω Γεω Γεω Γεω

    +

    ʻokina ʻokina ʻokina ʻokina ʻokina ʻokina ʻokina ʻokina ʻokina ʻokina ʻokina ʻokina ʻokina ʻokina ʻokina

    + + diff --git a/test/epubs-src/test_synthetic_unicode_glyphs/OEBPS/content.opf b/test/epubs-src/test_synthetic_unicode_glyphs/OEBPS/content.opf new file mode 100644 index 00000000..07018d88 --- /dev/null +++ b/test/epubs-src/test_synthetic_unicode_glyphs/OEBPS/content.opf @@ -0,0 +1,20 @@ + + + + urn:uuid:test-synthetic-unicode-glyphs + Test: Synthetic Unicode Glyphs + CrossPoint Test Fixture + en + + + + + + + + + + + + + diff --git a/test/epubs-src/test_synthetic_unicode_glyphs/OEBPS/nav.xhtml b/test/epubs-src/test_synthetic_unicode_glyphs/OEBPS/nav.xhtml new file mode 100644 index 00000000..ced747ff --- /dev/null +++ b/test/epubs-src/test_synthetic_unicode_glyphs/OEBPS/nav.xhtml @@ -0,0 +1,17 @@ + + + + + Test: Synthetic Unicode Glyphs + + + + + + diff --git a/test/epubs-src/test_synthetic_unicode_glyphs/OEBPS/styles/test.css b/test/epubs-src/test_synthetic_unicode_glyphs/OEBPS/styles/test.css new file mode 100644 index 00000000..82c69052 --- /dev/null +++ b/test/epubs-src/test_synthetic_unicode_glyphs/OEBPS/styles/test.css @@ -0,0 +1,19 @@ +body { + line-height: 1.35; +} + +p { + margin: 0 0 0.75em 0; +} + +.note { + font-style: italic; +} + +.sample { + margin-bottom: 0.5em; +} + +.big { + font-weight: bold; +} diff --git a/test/epubs-src/test_synthetic_unicode_glyphs/OEBPS/toc.ncx b/test/epubs-src/test_synthetic_unicode_glyphs/OEBPS/toc.ncx new file mode 100644 index 00000000..7d79a39e --- /dev/null +++ b/test/epubs-src/test_synthetic_unicode_glyphs/OEBPS/toc.ncx @@ -0,0 +1,22 @@ + + + + + + + + + + Test: Synthetic Unicode Glyphs + + + + Glyph Reference + + + + Block Stress Cases + + + + diff --git a/test/epubs-src/test_synthetic_unicode_glyphs/README.md b/test/epubs-src/test_synthetic_unicode_glyphs/README.md new file mode 100644 index 00000000..5c061b52 --- /dev/null +++ b/test/epubs-src/test_synthetic_unicode_glyphs/README.md @@ -0,0 +1,29 @@ +# Test: Synthetic Unicode Glyphs + +Source files for [`test/epubs/test_synthetic_unicode_glyphs.epub`](../../epubs/test_synthetic_unicode_glyphs.epub). + +This fixture covers the synthetic glyph paths added in commit `c4357d06e81e836c14126ff0fbd8adfc99e5da67`: + +- `U+2588` FULL BLOCK +- `U+25A0` BLACK SQUARE +- `U+0393` GREEK CAPITAL GAMMA +- `U+03B5` GREEK SMALL EPSILON +- `U+03C9` GREEK SMALL OMEGA +- `U+02BB` MODIFIER LETTER TURNED COMMA, aliased to `U+2018` + +Quick manual verification: + +1. Flash or run the build you want to test. +2. Open `test_synthetic_unicode_glyphs.epub`. +3. Check chapter 1 for labeled single-glyph and mixed inline samples. +4. Check chapter 2 for dense block runs near line wraps. +5. Watch logs while opening/rendering chapter 2; synthetic glyphs should not flood `GFX !! Outside range` messages. + +Rebuild the `.epub` after editing the source tree: + +```sh +cd test/epubs-src/test_synthetic_unicode_glyphs +rm -f ../../epubs/test_synthetic_unicode_glyphs.epub +zip -X0 ../../epubs/test_synthetic_unicode_glyphs.epub mimetype +zip -Xr9D ../../epubs/test_synthetic_unicode_glyphs.epub META-INF OEBPS +``` diff --git a/test/epubs-src/test_synthetic_unicode_glyphs/mimetype b/test/epubs-src/test_synthetic_unicode_glyphs/mimetype new file mode 100644 index 00000000..403c4f02 --- /dev/null +++ b/test/epubs-src/test_synthetic_unicode_glyphs/mimetype @@ -0,0 +1 @@ +application/epub+zip diff --git a/test/epubs/test_synthetic_unicode_glyphs.epub b/test/epubs/test_synthetic_unicode_glyphs.epub new file mode 100644 index 00000000..3d64e6ae Binary files /dev/null and b/test/epubs/test_synthetic_unicode_glyphs.epub differ diff --git a/test/hyphenation_eval/HyphenationEvaluationTest.cpp b/test/hyphenation_eval/HyphenationEvaluationTest.cpp index a56467a9..27b9fff3 100644 --- a/test/hyphenation_eval/HyphenationEvaluationTest.cpp +++ b/test/hyphenation_eval/HyphenationEvaluationTest.cpp @@ -44,6 +44,7 @@ const std::vector kSupportedLanguages = { {"russian", "test/hyphenation_eval/resources/russian_hyphenation_tests.txt", "ru"}, {"spanish", "test/hyphenation_eval/resources/spanish_hyphenation_tests.txt", "es"}, {"italian", "test/hyphenation_eval/resources/italian_hyphenation_tests.txt", "it"}, + {"polish", "test/hyphenation_eval/resources/polish_hyphenation_tests.txt", "pl"}, }; std::vector expectedPositionsFromAnnotatedWord(const std::string& annotated) { diff --git a/test/hyphenation_eval/resources/generate_hyphenation_test_data.py b/test/hyphenation_eval/resources/generate_hyphenation_test_data.py index ec38ee67..288336e1 100644 --- a/test/hyphenation_eval/resources/generate_hyphenation_test_data.py +++ b/test/hyphenation_eval/resources/generate_hyphenation_test_data.py @@ -16,7 +16,6 @@ import argparse import re from collections import Counter -import pyphen from pathlib import Path import zipfile @@ -75,6 +74,8 @@ def generate_hyphenation_data( min_prefix: Minimum characters allowed before the first hyphen (default: 2) min_suffix: Minimum characters allowed after the last hyphen (default: 2) """ + import pyphen + print(f"Reading from: {input_file}") # Read the input file diff --git a/test/hyphenation_eval/resources/polish_hyphenation_tests.txt b/test/hyphenation_eval/resources/polish_hyphenation_tests.txt new file mode 100644 index 00000000..5bc8738f --- /dev/null +++ b/test/hyphenation_eval/resources/polish_hyphenation_tests.txt @@ -0,0 +1,5012 @@ +# Hyphenation Test Data +# Source: Władysław Stanisław Reymont, Chlopi.epub +# Language: pl_PL +# Min prefix: 2 +# Min suffix: 2 +# Total words: 5000 +# Format: word | hyphenated_form | frequency_in_source +# +# Hyphenation points are marked with '=' +# Example: Silbentrennung -> Sil=ben=tren=nung +# + +edytorski|edy=tor=ski|3556 +przypis|przy=pis|3556 +jeszcze|jesz=cze|1046 +wszystko|wszyst=ko|411 +któren|któ=ren|292 +będzie|bę=dzie|285 +ledwie|le=d=wie|267 +wszystkie|wszyst=kie|259 +chałupy|cha=łu=py|252 +ludzie|lu=dzie|245 +cięgiem|cię=giem|240 +dzieci|dzie=ci|239 +dobrze|do=brze|228 +siebie|sie=bie|218 +dopiero|do=pie=ro|212 +Mateusz|Ma=te=usz|210 +całkiem|cał=kiem|200 +Boryna|Bo=ry=na|199 +prawie|pra=wie|195 +wszystkich|wszyst=kich|191 +świecie|świe=cie|183 +dzisiaj|dzi=siaj|164 +choćby|choć=by|161 +roboty|ro=bo=ty|161 +chałupie|cha=łu=pie|158 +drzewa|drze=wa|153 +drugie|dru=gie|151 +słońce|słoń=ce|150 +powiedział|po=wie=dział|149 +wszyscy|wszy=scy|148 +Jagustynka|Ja=gu=styn=ka|146 +poszedł|po=szedł|145 +kobiety|ko=bie=ty|143 +ździebko|ździeb=ko|142 +ksiądz|ksiądz|140 +barzej|ba=rzej|139 +jakieś|ja=kieś|139 +między|mię=dzy|139 +niekiedy|nie=kie=dy|138 +oczami|ocza=mi|137 +chciał|chciał|134 +Jagusia|Ja=gu=sia|134 +ziemię|zie=mię|128 +chociaż|cho=ciaż|123 +więcej|wię=cej|122 +gdzieś|gdzieś|120 +ciężko|cięż=ko|117 +drodze|dro=dze|115 +wszystkim|wszyst=kim|115 +bowiem|bo=wiem|114 +stronę|stro=nę|113 +wielce|wiel=ce|113 +poszła|po=szła|111 +kościoła|ko=ścio=ła|109 +przecież|prze=cież|109 +często|czę=sto|107 +wiater|wia=ter|105 +czasem|cza=sem|103 +prosto|pro=sto|101 +rzucił|rzu=cił|101 +szepnął|szep=nął|101 +głowie|gło=wie|99 +jakoby|ja=ko=by|99 +Grzela|Grze=la|98 +potrza|po=trza|97 +zaczął|za=czął|97 +każden|każ=den|96 +Szymek|Szy=mek|96 +gdziesik|gdzie=sik|94 +gdzieniegdzie|gdzie=nie=gdzie|93 +Jambroży|Jam=bro=ży|93 +ściany|ścia=ny|93 +człowiek|czło=wiek|92 +Prawda|Praw=da|91 +ktosik|kto=sik|90 +prędko|pręd=ko|90 +ciebie|cie=bie|89 +karczmy|karcz=my|89 +Dominikowa|Do=mi=ni=ko=wa|88 +kościele|ko=ście=le|88 +pacierz|pa=cierz|88 +szepnęła|szep=nę=ła|88 +rzekła|rze=kła|87 +właśnie|wła=śnie|87 +dziedzic|dzie=dzic|86 +jednak|jed=nak|86 +prawda|praw=da|85 +drogach|dro=gach|84 +siedział|sie=dział|84 +miejsca|miej=sca|83 +drugich|dru=gich|82 +narodu|na=ro=du|82 +Pietrek|Pie=trek|82 +pilnie|pil=nie|80 +przódzi|przó=dzi|80 +zawołał|za=wo=łał|80 +ziemia|zie=mia|80 +głosem|gło=sem|79 +przeciek|prze=ciek|79 +srodze|sro=dze|79 +krzyknął|krzyk=nął|78 +oczyma|oczy=ma|78 +pewnie|pew=nie|78 +rychło|ry=chło|78 +lepiej|le=piej|77 +piersi|pier=si|77 +żałośnie|ża=ło=śnie|76 +niekaj|nie=kaj|75 +wszędy|wszę=dy|75 +trochę|tro=chę|74 +trzeba|trze=ba|74 +chałupach|cha=łu=pach|73 +pierwszy|pierw=szy|73 +wiedział|wie=dział|73 +gorąco|go=rą=co|72 +swojego|swo=je=go|72 +złości|zło=ści|72 +wskroś|wskroś|71 +karczmie|karcz=mie|69 +którym|któ=rym|69 +prędzej|prę=dzej|69 +płaczem|pła=czem|69 +wczoraj|wczo=raj|69 +dziwnie|dziw=nie|68 +Jagusi|Ja=gu=si|68 +zaczęła|za=czę=ła|68 +niezgorzej|nie=zgo=rzej|67 +swoich|swo=ich|67 +ślepiami|śle=pia=mi|67 +krzykiem|krzy=kiem|66 +musiał|mu=siał|66 +przeciech|prze=ciech|66 +przyszedł|przy=szedł|66 +słońcu|słoń=cu|66 +deszcz|deszcz|65 +oczach|oczach|65 +sielnie|siel=nie|65 +wyszedł|wy=szedł|65 +ziemniaki|ziem=nia=ki|64 +Borynów|Bo=ry=nów|63 +cichości|ci=cho=ści|63 +Dopiero|Do=pie=ro|63 +których|któ=rych|63 +podniósł|pod=niósł|63 +polach|po=lach|63 +słychać|sły=chać|63 +twarzy|twa=rzy|63 +chłopaki|chło=pa=ki|62 +dokoła|do=ko=ła|62 +drzewo|drze=wo|62 +Lipcach|Lip=cach|62 +WolneLektury|Wol=ne=Lek=tu=ry|62 +światła|świa=tła|62 +której|któ=rej|61 +spokojnie|spo=koj=nie|61 +starego|sta=re=go|61 +wesoło|we=so=ło|61 +widział|wi=dział|61 +wszystkiego|wszyst=kie=go|61 +głośno|gło=śno|60 +niekiej|nie=kiej|60 +śmierć|śmierć|60 +chciała|chcia=ła|59 +ciągle|cią=gle|59 +kobiet|ko=biet|59 +leżały|le=ża=ły|59 +bacząc|ba=cząc|58 +cichość|ci=chość|58 +samego|sa=me=go|58 +siedziała|sie=dzia=ła|58 +zawsze|za=wsze|58 +gospodarz|go=spo=darz|57 +nieraz|nie=raz|57 +nikogo|ni=ko=go|57 +sposób|spo=sób|57 +Słońce|Słoń=ce|57 +niecoś|nie=coś|56 +radośnie|ra=do=śnie|56 +strony|stro=ny|56 +trzęsły|trzę=sły|56 +wszyćko|wszyć=ko|56 +zapłać|za=płać|56 +chałupę|cha=łu=pę|55 +chłopy|chło=py|55 +niczego|ni=cze=go|55 +opłotkach|opłot=kach|55 +Pewnie|Pew=nie|55 +szeptał|szep=tał|55 +ciszej|ci=szej|54 +gospodarze|go=spo=da=rze|54 +jednego|jed=ne=go|54 +krzywdy|krzyw=dy|54 +niebie|nie=bie|54 +niejeden|nie=je=den|54 +stawem|sta=wem|54 +czerwone|czer=wo=ne|53 +Dominikowej|Do=mi=ni=ko=wej|53 +drugiej|dru=giej|53 +kobieta|ko=bie=ta|53 +opłotki|opłot=ki|53 +przyjdzie|przyj=dzie|53 +siedzieli|sie=dzie=li|53 +stojał|sto=jał|53 +strach|strach|53 +takiego|ta=kie=go|53 +chłopa|chło=pa|52 +ciężkie|cięż=kie|52 +księdza|księ=dza|52 +miasta|mia=sta|52 +chłopak|chło=pak|51 +długie|dłu=gie|51 +któryś|któ=ryś|51 +ruszył|ru=szył|51 +wieczór|wie=czór|51 +zawżdy|za=wż=dy|51 +śmierci|śmier=ci|51 +chałupą|cha=łu=pą|50 +chwili|chwi=li|50 +dobrodziej|do=bro=dziej|50 +powietrzu|po=wie=trzu|50 +pójdzie|pój=dzie|50 +robota|ro=bo=ta|50 +stronie|stro=nie|50 +trudno|trud=no|50 +trwożnie|trwoż=nie|50 +chodził|cho=dził|49 +Dobrze|Do=brze|49 +zacząć|za=cząć|49 +zaczęli|za=czę=li|49 +chłopów|chło=pów|48 +leciały|le=cia=ły|48 +osobna|osob=na|48 +pieniądze|pie=nią=dze|48 +wzięła|wzię=ła|48 +drugiego|dru=gie=go|47 +jeszczek|jesz=czek|47 +którego|któ=re=go|47 +mruknął|mruk=nął|47 +młynarz|mły=narz|47 +naprzeciw|na=prze=ciw|47 +poszli|po=szli|47 +powietrze|po=wie=trze|47 +swojej|swo=jej|47 +wiadomo|wia=do=mo|47 +wreszcie|wresz=cie|47 +zapytał|za=py=tał|47 +święta|świę=ta|47 +chciało|chcia=ło|46 +każdego|każ=de=go|46 +miejscu|miej=scu|46 +myślał|my=ślał|46 +podnosił|pod=no=sił|46 +powiedziała|po=wie=dzia=ła|46 +stajni|staj=ni|46 +wszedł|wszedł|46 +zarówno|za=rów=no|46 +dojrzał|doj=rzał|45 +dziecko|dziec=ko|45 +jakimś|ja=kimś|45 +Jeszcze|Jesz=cze|45 +każdym|każ=dym|45 +przysiadł|przy=siadł|45 +sadach|sa=dach|45 +siedzi|sie=dzi|45 +wesele|we=se=le|45 +drugimi|dru=gi=mi|44 +Kłębów|Kłę=bów|44 +ludźmi|ludź=mi|44 +pacierze|pa=cie=rze|44 +patrzeć|pa=trzeć|44 +poredzić|po=re=dzić|44 +Płoszka|Płosz=ka|44 +radości|ra=do=ści|44 +wiecie|wie=cie|44 +wszystek|wszy=stek|44 +zrobił|zro=bił|44 +złotych|zło=tych|44 +złością|zło=ścią|44 +śmiechem|śmie=chem|44 +bardziej|bar=dziej|43 +dziwota|dzi=wo=ta|43 +Jambroż|Jam=broż|43 +prawdę|praw=dę|43 +sadzie|sa=dzie|43 +zaczęły|za=czę=ły|43 +światem|świa=tem|43 +chodzi|cho=dzi|42 +cichuśko|ci=chuś=ko|42 +gospodarzy|go=spo=da=rzy|42 +każdemu|każ=de=mu|42 +krzyczał|krzy=czał|42 +leciał|le=ciał|42 +Michał|Mi=chał|42 +myślała|my=śla=ła|42 +mówili|mó=wi=li|42 +przyszła|przy=szła|42 +sprawy|spra=wy|42 +swojemu|swo=je=mu|42 +szukać|szu=kać|42 +słowem|sło=wem|42 +wszędzie|wszę=dzie|42 +świata|świa=ta|42 +światło|świa=tło|42 +drugim|dru=gim|41 +krzyczeć|krzy=czeć|41 +krzyki|krzy=ki|41 +leżała|le=ża=ła|41 +patrzył|pa=trzył|41 +patrząc|pa=trząc|41 +pieniędzy|pie=nię=dzy|41 +próżno|próż=no|41 +sprawa|spra=wa|41 +grosza|gro=sza|40 +Kozłowa|Ko=zło=wa|40 +lubością|lu=bo=ścią|40 +Maciej|Ma=ciej|40 +pięściami|pię=ścia=mi|40 +poleciał|po=le=ciał|40 +przyszło|przy=szło|40 +radością|ra=do=ścią|40 +skoczył|sko=czył|40 +ścianą|ścia=ną|40 +bliżej|bli=żej|39 +Jagusię|Ja=gu=się|39 +jestem|je=stem|39 +kominem|ko=mi=nem|39 +komory|ko=mo=ry|39 +krzyknęła|krzyk=nę=ła|39 +któraś|któ=raś|39 +niemało|nie=ma=ło|39 +powiadał|po=wia=dał|39 +radość|ra=dość|39 +szeroko|sze=ro=ko|39 +słońca|słoń=ca|39 +zawołała|za=wo=ła=ła|39 +śniegi|śnie=gi|39 +Boryny|Bo=ry=ny|38 +daleko|da=le=ko|38 +dworze|dwo=rze|38 +dzieje|dzie=je|38 +dziećmi|dzieć=mi|38 +groźnie|groź=nie|38 +jednej|jed=nej|38 +miejsce|miej=sce|38 +milczeniu|mil=cze=niu|38 +niczym|ni=czym|38 +potrzeba|po=trze=ba|38 +rzeczy|rze=czy|38 +strachu|stra=chu|38 +westchnął|wes=tchnął|38 +Wiecie|Wie=cie|38 +wiedząc|wie=dząc|38 +ziemniaków|ziem=nia=ków|38 +zrobić|zro=bić|38 +chałup|cha=łup|37 +chwila|chwi=la|37 +dobrego|do=bre=go|37 +każdej|każ=dej|37 +musiała|mu=sia=ła|37 +Nastka|Na=st=ka|37 +nowego|no=we=go|37 +podwórze|po=dwó=rze|37 +robotę|ro=bo=tę|37 +różnych|róż=nych|37 +stodoły|sto=do=ły|37 +słuchał|słu=chał|37 +zaglądał|za=glą=dał|37 +ziemie|zie=mie|37 +zwiesnę|zwie=snę|37 +bardzo|bar=dzo|36 +całego|ca=łe=go|36 +czasami|cza=sa=mi|36 +dojrzawszy|doj=rzaw=szy|36 +powiada|po=wia=da|36 +pośrodku|po=środ=ku|36 +strasznie|strasz=nie|36 +wszystkimi|wszyst=ki=mi|36 +wywarte|wy=war=te|36 +zajrzał|zaj=rzał|36 +Bogiem|Bo=giem|35 +chwilę|chwi=lę|35 +chłopaków|chło=pa=ków|35 +czarne|czar=ne|35 +cztery|czte=ry|35 +głupie|głu=pie|35 +jednym|jed=nym|35 +Ksiądz|Ksiądz|35 +morgów|mor=gów|35 +ostała|osta=ła|35 +pierwsze|pierw=sze|35 +poradzić|po=ra=dzić|35 +poredzi|po=re=dzi|35 +powiem|po=wiem|35 +powrotem|po=wro=tem|35 +prawił|pra=wił|35 +różności|róż=no=ści|35 +smutnie|smut=nie|35 +strachem|stra=chem|35 +szmaty|szma=ty|35 +słyszał|sły=szał|35 +takich|ta=kich|35 +takiej|ta=kiej|35 +trzęsła|trzę=sła|35 +zrobiło|zro=bi=ło|35 +ścianę|ścia=nę|35 +ślepie|śle=pie|35 +święty|świę=ty|35 +chyłkiem|chył=kiem|34 +czegoś|cze=goś|34 +dzieuchy|dzieu=chy|34 +dziewczyny|dziew=czy=ny|34 +galanto|ga=lan=to|34 +Jagusią|Ja=gu=sią|34 +nikomu|ni=ko=mu|34 +nogami|no=ga=mi|34 +patrzy|pa=trzy|34 +Tereska|Te=re=ska|34 +zawzięcie|za=wzię=cie|34 +zwyczajnie|zwy=czaj=nie|34 +śniegu|śnie=gu|34 +chleba|chle=ba|33 +czasie|cza=sie|33 +Jędrzych|Ję=drzych|33 +kulasy|ku=la=sy|33 +Mateusza|Ma=te=usza|33 +mówiła|mó=wi=ła|33 +niedzielę|nie=dzie=lę|33 +organista|or=ga=ni=sta|33 +parobek|pa=ro=bek|33 +podniesła|pod=nie=sła|33 +poleciała|po=le=cia=ła|33 +puścił|pu=ścił|33 +resztę|resz=tę|33 +rozglądając|roz=glą=da=jąc|33 +Właśnie|Wła=śnie|33 +chałupami|cha=łu=pa=mi|32 +czekał|cze=kał|32 +drzewami|drze=wa=mi|32 +kiejby|kiej=by|32 +kościołem|ko=ścio=łem|32 +muzyka|mu=zy=ka|32 +nieśmiało|nie=śmia=ło|32 +organistów|or=ga=ni=stów|32 +organiścina|or=ga=ni=ści=na|32 +owdzie|owdzie|32 +parobków|pa=rob=ków|32 +patrzał|pa=trzał|32 +powiedzieć|po=wie=dzieć|32 +prosił|pro=sił|32 +Przeciech|Prze=ciech|32 +ramiona|ra=mio=na|32 +rychtyk|rych=tyk|32 +Stacho|Sta=cho|32 +tylachna|ty=lach=na|32 +uciechy|ucie=chy|32 +wysoko|wy=so=ko|32 +wyszła|wy=szła|32 +Zdrowaś|Zdro=waś|32 +ziemią|zie=mią|32 +świętą|świę=tą|32 +gałęzie|ga=łę=zie|31 +głównie|głów=nie|31 +Jankiel|Jan=kiel|31 +kamień|ka=mień|31 +kościół|ko=ściół|31 +Kłębowa|Kłę=bo=wa|31 +miejscami|miej=sca=mi|31 +niesły|nie=sły|31 +ogniem|ogniem|31 +ruchać|ru=chać|31 +spiesznie|spiesz=nie|31 +stanął|sta=nął|31 +szeptała|szep=ta=ła|31 +wcześniej|wcze=śniej|31 +westchnęła|wes=tchnę=ła|31 +wrzask|wrzask|31 +wzdychał|wzdy=chał|31 +akuratnie|aku=rat=nie|30 +chodziła|cho=dzi=ła|30 +chycił|chy=cił|30 +ciepło|cie=pło|30 +garści|gar=ści|30 +gwiazdy|gwiaz=dy|30 +głęboko|głę=bo=ko|30 +kamienie|ka=mie=nie|30 +kominie|ko=mi=nie|30 +młynarza|mły=na=rza|30 +rzuciła|rzu=ci=ła|30 +widząc|wi=dząc|30 +wieczora|wie=czo=ra|30 +wiedziała|wie=dzia=ła|30 +wkrótce|wkrót=ce|30 +wrzaski|wrza=ski|30 +zajadle|za=ja=dle|30 +zarazem|za=ra=zem|30 +zdziebko|zdzieb=ko|30 +zmierzchu|zmierz=chu|30 +święte|świę=te|30 +buchnęła|buch=nę=ła|29 +Bylica|By=li=ca|29 +cienie|cie=nie|29 +ciągnął|cią=gnął|29 +czynił|czy=nił|29 +dobrodzieja|do=bro=dzie=ja|29 +galancie|ga=lan=cie|29 +godzinę|go=dzi=nę|29 +gorzałki|go=rzał=ki|29 +głośniej|gło=śniej|29 +kieliszek|kie=li=szek|29 +kolana|ko=la=na|29 +nasłuchując|na=słu=chu=jąc|29 +pazurami|pa=zu=ra=mi|29 +pięścią|pię=ścią|29 +podwórzu|po=dwó=rzu|29 +pojechał|po=je=chał|29 +poredziła|po=re=dzi=ła|29 +prosiła|pro=si=ła|29 +przystawał|przy=sta=wał|29 +robocie|ro=bo=cie|29 +sołtys|soł=tys|29 +spojrzał|spoj=rzał|29 +spytała|spy=ta=ła|29 +stanie|sta=nie|29 +stanęła|sta=nę=ła|29 +słuchać|słu=chać|29 +urągliwie|urą=gli=wie|29 +weźmie|weź=mie|29 +widziało|wi=dzia=ło|29 +wiedzą|wie=dzą|29 +wielkim|wiel=kim|29 +wrócił|wró=cił|29 +wzięli|wzię=li|29 +wójtowa|wój=to=wa|29 +zapamiętale|za=pa=mię=ta=le|29 +śpiewał|śpie=wał|29 +boleśnie|bo=le=śnie|28 +częściej|czę=ściej|28 +człowieka|czło=wie=ka|28 +drudzy|dru=dzy|28 +drzwiami|drzwia=mi|28 +dziesięć|dzie=sięć|28 +gospodyni|go=spo=dy=ni|28 +jeszczech|jesz=czech|28 +którzy|któ=rzy|28 +Macieju|Ma=cie=ju|28 +odrzekł|od=rzekł|28 +ozwała|ozwa=ła|28 +pierwsza|pierw=sza|28 +pierzynę|pie=rzy=nę|28 +pieski|pie=ski|28 +podnosząc|pod=no=sząc|28 +pokrótce|po=krót=ce|28 +przeszedł|prze=szedł|28 +przyleciał|przy=le=ciał|28 +spokój|spo=kój|28 +spowiedzi|spo=wie=dzi|28 +starszy|star=szy|28 +uciekać|ucie=kać|28 +wicher|wi=cher|28 +zwiesna|zwie=sna|28 +śmiała|śmia=ła|28 +śpiesznie|śpiesz=nie|28 +świnie|świ=nie|28 +dziewczyna|dziew=czy=na|27 +gorzałkę|go=rzał=kę|27 +głupia|głu=pia|27 +Jasiek|Ja=siek|27 +koniec|ko=niec|27 +koszuli|ko=szu=li|27 +Lipiec|Li=piec|27 +ludziach|lu=dziach|27 +naprzód|na=przód|27 +Nastusia|Na=stu=sia|27 +odrzekła|od=rze=kła|27 +pacierza|pa=cie=rza|27 +powiadali|po=wia=da=li|27 +przeciw|prze=ciw|27 +płacze|pła=cze|27 +Płoszkowa|Płosz=ko=wa|27 +sercem|ser=cem|27 +starym|sta=rym|27 +widziała|wi=dzia=ła|27 +wieczorem|wie=czo=rem|27 +Wszystkie|Wszyst=kie|27 +wykrzyknął|wy=krzyk=nął|27 +znowuj|zno=wuj|27 +świętej|świę=tej|27 +białych|bia=łych|26 +brakowało|bra=ko=wa=ło|26 +buchnął|buch=nął|26 +byście|by=ście|26 +będziesz|bę=dziesz|26 +chmury|chmu=ry|26 +dziedzica|dzie=dzi=ca|26 +dzisia|dzi=sia|26 +kapoty|ka=po=ty|26 +krzywda|krzyw=da|26 +obrazy|ob=ra=zy|26 +pomocy|po=mo=cy|26 +porwał|po=rwał|26 +proboszcz|pro=boszcz|26 +rzucał|rzu=cał|26 +sprawę|spra=wę|26 +szybko|szyb=ko|26 +twardo|twar=do|26 +wrzaskiem|wrza=skiem|26 +wychodził|wy=cho=dził|26 +zagony|za=go=ny|26 +zerwał|ze=rwał|26 +śmiechu|śmie=chu|26 +śmiechy|śmie=chy|26 +bezwolnie|bez=wol=nie|25 +cichym|ci=chym|25 +czerwonych|czer=wo=nych|25 +dookoła|do=oko=ła|25 +gotowa|go=to=wa|25 +głowami|gło=wa=mi|25 +komina|ko=mi=na|25 +krzywdę|krzyw=dę|25 +krzyżem|krzy=żem|25 +mrokach|mro=kach|25 +mruczał|mru=czał|25 +najbarzej|naj=ba=rzej|25 +nazajutrz|na=za=jutrz|25 +niespokojnie|nie=spo=koj=nie|25 +ostatnie|ostat=nie|25 +ostatnią|ostat=nią|25 +ostrożnie|ostroż=nie|25 +pamięci|pa=mię=ci|25 +patrzała|pa=trza=ła|25 +pewnikiem|pew=ni=kiem|25 +pierwszą|pierw=szą|25 +Pietrka|Pietr=ka|25 +przeżegnał|prze=że=gnał|25 +później|póź=niej|25 +reszta|resz=ta|25 +rozlegał|roz=le=gał|25 +sieniach|sie=niach|25 +Weronka|We=ron=ka|25 +wszystkiemu|wszyst=kie=mu|25 +wyrwał|wy=rwał|25 +zapaskę|za=pa=skę|25 +śniegiem|śnie=giem|25 +Bartek|Bar=tek|24 +bliska|bli=ska|24 +bronił|bro=nił|24 +buchały|bu=cha=ły|24 +cieniu|cie=niu|24 +cierpliwie|cier=pli=wie|24 +dzieciom|dzie=ciom|24 +dłużej|dłu=żej|24 +grzech|grzech|24 +indziej|in=dziej|24 +kochany|ko=cha=ny|24 +kożuch|ko=żuch|24 +kątach|ką=tach|24 +leciała|le=cia=ła|24 +lękliwie|lę=kli=wie|24 +niechby|niech=by|24 +ogromne|ogrom=ne|24 +poradzi|po=ra=dzi|24 +przystanął|przy=sta=nął|24 +przyszli|przy=szli|24 +robiła|ro=bi=ła|24 +rozumu|ro=zu=mu|24 +trzyma|trzy=ma|24 +trzymał|trzy=mał|24 +wielki|wiel=ki|24 +wisiało|wi=sia=ło|24 +wodzie|wo=dzie|24 +wprost|wprost|24 +wypowiedzieć|wy=po=wie=dzieć|24 +wójtów|wój=tów|24 +zabrał|za=brał|24 +ścianami|ścia=na=mi|24 +środku|środ=ku|24 +bieżyć|bie=żyć|23 +bociek|bo=ciek|23 +błocie|bło=cie|23 +chocia|cho=cia|23 +ciepły|cie=pły|23 +drzewiny|drze=wi=ny|23 +Jagustynki|Ja=gu=styn=ki|23 +jęknęła|jęk=nę=ła|23 +kochane|ko=cha=ne|23 +mocniej|moc=niej|23 +oglądać|oglą=dać|23 +organy|or=ga=ny|23 +ostatni|ostat=ni|23 +pacierzy|pa=cie=rzy|23 +papierosa|pa=pie=ro=sa|23 +pazury|pa=zu=ry|23 +portki|por=t=ki|23 +powrócił|po=wró=cił|23 +południe|po=łu=dnie|23 +przyniósł|przy=niósł|23 +płotem|pło=tem|23 +rzeknę|rzek=nę|23 +siarczyście|siar=czy=ście|23 +siedzą|sie=dzą|23 +stawie|sta=wie|23 +trzech|trzech|23 +twarze|twa=rze|23 +tydzień|ty=dzień|23 +wykrzyknęła|wy=krzyk=nę=ła|23 +wynosił|wy=no=sił|23 +wytrzymać|wy=trzy=mać|23 +zakrzyczał|za=krzy=czał|23 +zdołać|zdo=łać|23 +zdrowie|zdro=wie|23 +świeciło|świe=ci=ło|23 +świtania|świ=ta=nia|23 +Antkowi|Ant=ko=wi|22 +bronić|bro=nić|22 +chcieli|chcie=li|22 +czapkę|czap=kę|22 +dzwony|dzwo=ny|22 +gotowy|go=to=wy|22 +głosów|gło=sów|22 +głucho|głu=cho|22 +Głupiś|Głu=piś|22 +inaczej|ina=czej|22 +innego|in=ne=go|22 +inszego|in=sze=go|22 +kiedyś|kie=dyś|22 +kowala|ko=wa=la|22 +krzyczała|krzy=cza=ła|22 +kurzawy|ku=rza=wy|22 +Ludzie|Lu=dzie|22 +niechta|nie=chta|22 +niejednemu|nie=jed=ne=mu|22 +ojcowej|oj=co=wej|22 +organisty|or=ga=ni=sty|22 +pomiędzy|po=mię=dzy|22 +poredził|po=re=dził|22 +poszły|po=szły|22 +Prawdę|Praw=dę|22 +przodem|przo=dem|22 +przychodzi|przy=cho=dzi|22 +płotów|pło=tów|22 +roznosiły|roz=no=si=ły|22 +rychlej|ry=chlej|22 +również|rów=nież|22 +szkoda|szko=da|22 +topolowej|to=po=lo=wej|22 +wojska|woj=ska|22 +wołała|wo=ła=ła|22 +żadnej|żad=nej|22 +chodziły|cho=dzi=ły|21 +chorego|cho=re=go|21 +chycić|chy=cić|21 +chłopaka|chło=pa=ka|21 +czerwony|czer=wo=ny|21 +dobrym|do=brym|21 +Dominikową|Do=mi=ni=ko=wą|21 +działo|dzia=ło|21 +dziedzicem|dzie=dzi=cem|21 +gromadą|gro=ma=dą|21 +jakiejś|ja=kiejś|21 +jednako|jed=na=ko|21 +komorze|ko=mo=rze|21 +kręcił|krę=cił|21 +latały|la=ta=ły|21 +ludziom|lu=dziom|21 +nabożeństwo|na=bo=żeń=stwo|21 +nasłuchiwał|na=słu=chi=wał|21 +odpowiadał|od=po=wia=dał|21 +ogromny|ogrom=ny|21 +plebanię|ple=ba=nię|21 +począć|po=cząć|21 +poniektóre|po=nie=któ=re|21 +przerwał|prze=rwał|21 +przystając|przy=sta=jąc|21 +rozeznać|ro=ze=znać|21 +rozlegały|roz=le=ga=ły|21 +ruszyli|ru=szy=li|21 +skrzydłami|skrzy=dła=mi|21 +stawał|sta=wał|21 +uważnie|uważ=nie|21 +wargami|war=ga=mi|21 +wniebogłosy|wnie=bo=gło=sy|21 +wracają|wra=ca=ją|21 +wrzasnął|wrza=snął|21 +wrzasnęła|wrza=snę=ła|21 +wyniósł|wy=niósł|21 +wyraźnie|wy=raź=nie|21 +zagonach|za=go=nach|21 +zajrzeć|zaj=rzeć|21 +zapomnieć|za=po=mnieć|21 +śniegów|śnie=gów|21 +śpiewały|śpie=wa=ły|21 +świnia|świ=nia|21 +całował|ca=ło=wał|20 +chodzili|cho=dzi=li|20 +ciężki|cięż=ki|20 +czekać|cze=kać|20 +daleka|da=le=ka|20 +dalekie|da=le=kie|20 +dorzucił|do=rzu=cił|20 +dzieciątko|dzie=ciąt=ko|20 +gałęzi|ga=łę=zi|20 +historie|hi=sto=rie|20 +jakiego|ja=kie=go|20 +jarmarku|jar=mar=ku|20 +jaskółki|ja=skół=ki|20 +kapotę|ka=po=tę|20 +kiełbasy|kieł=ba=sy|20 +kolacji|ko=la=cji|20 +kowalowa|ko=wa=lo=wa|20 +krótko|krót=ko|20 +lecieć|le=cieć|20 +naczelnik|na=czel=nik|20 +nieustannie|nie=ustan=nie|20 +odwrócił|od=wró=cił|20 +oknami|okna=mi|20 +ostało|osta=ło|20 +pamięć|pa=mięć|20 +pijany|pi=ja=ny|20 +podnosiła|pod=no=si=ła|20 +podnosiły|pod=no=si=ły|20 +podwórza|po=dwó=rza|20 +pomagać|po=ma=gać|20 +pomarł|po=marł|20 +powieda|po=wie=da|20 +powstał|po=wstał|20 +południa|po=łu=dnia|20 +rozglądał|roz=glą=dał|20 +rękach|rę=kach|20 +skrzyni|skrzy=ni|20 +smętarz|smę=tarz|20 +sprawiedliwości|spra=wie=dli=wo=ści|20 +starczyło|star=czy=ło|20 +szukał|szu=kał|20 +topoli|to=po=li|20 +trzasnął|trza=snął|20 +trzydzieści|trzy=dzie=ści|20 +ukradkiem|ukrad=kiem|20 +uroczyście|uro=czy=ście|20 +wiedzieć|wie=dzieć|20 +Witkiem|Wit=kiem|20 +wodził|wo=dził|20 +wsiach|wsiach|20 +Wszystko|Wszyst=ko|20 +wyciągnął|wy=cią=gnął|20 +wójtem|wój=tem|20 +zaśmiał|za=śmiał|20 +zielone|zie=lo=ne|20 +Antkiem|Ant=kiem|19 +blisko|bli=sko|19 +borowy|bo=ro=wy|19 +buchał|bu=chał|19 +całować|ca=ło=wać|19 +Cichoj|Ci=choj|19 +cmentarz|cmen=tarz|19 +czterech|czte=rech|19 +czyste|czy=ste|19 +garście|gar=ście|19 +gdzież|gdzież|19 +gniewnie|gniew=nie|19 +godziny|go=dzi=ny|19 +Grzeli|Grze=li|19 +głuche|głu=che|19 +głuchy|głu=chy|19 +Jambroża|Jam=bro=ża|19 +kancelarii|kan=ce=la=rii|19 +Kobiety|Ko=bie=ty|19 +kreminale|kre=mi=na=le|19 +kroków|kro=ków|19 +kużden|kuż=den|19 +lipeckie|li=pec=kie|19 +Mateuszem|Ma=te=uszem|19 +Moiściewy|Mo=iście=wy|19 +młynem|mły=nem|19 +narodem|na=ro=dem|19 +Nastuś|Na=stuś|19 +nogach|no=gach|19 +obejrzał|obej=rzał|19 +obiedzie|obie=dzie|19 +pobiegła|po=bie=gła|19 +poczciwie|po=czci=wie|19 +poczuła|po=czu=ła|19 +polami|po=la=mi|19 +poniesła|po=nie=sła|19 +przebierać|prze=bie=rać|19 +przechodził|prze=cho=dził|19 +przedzie|prze=dzie|19 +przejmował|przej=mo=wał|19 +puszczą|pusz=czą|19 +płynął|pły=nął|19 +rozgłośnie|roz=gło=śnie|19 +rozległ|roz=legł|19 +rozmowy|roz=mo=wy|19 +rozpalone|roz=pa=lo=ne|19 +rękoma|rę=ko=ma|19 +samych|sa=mych|19 +serdecznie|ser=decz=nie|19 +skończyła|skoń=czy=ła|19 +starszych|star=szych|19 +turbacji|tur=ba=cji|19 +urzędu|urzę=du|19 +wichurą|wi=chu=rą|19 +widzieli|wi=dzie=li|19 +wiosna|wio=sna|19 +większy|więk=szy|19 +wymiarkować|wy=miar=ko=wać|19 +wyniesła|wy=nie=sła|19 +zajrzała|zaj=rza=ła|19 +zakrzyczała|za=krzy=cza=ła|19 +znajdzie|znaj=dzie|19 +zrobiła|zro=bi=ła|19 +łacniej|łac=niej|19 +ściskał|ści=skał|19 +śniegach|śnie=gach|19 +bierze|bie=rze|18 +Borynie|Bo=ry=nie|18 +błyskały|bły=ska=ły|18 +chcecie|chce=cie|18 +chłopami|chło=pa=mi|18 +cieniach|cie=niach|18 +ciągnęły|cią=gnę=ły|18 +czytał|czy=tał|18 +Dajcie|Daj=cie|18 +deliberować|de=li=be=ro=wać|18 +dobrodziejowi|do=bro=dzie=jo=wi|18 +dwadzieścia|dwa=dzie=ścia|18 +dworskie|dwor=skie|18 +dzwoniły|dzwo=ni=ły|18 +gorzały|go=rza=ły|18 +gorzej|go=rzej|18 +grontu|gron=tu|18 +Jagnie|Ja=gnie|18 +jakiegoś|ja=kie=goś|18 +Jantoś|Jan=toś|18 +książki|książ=ki|18 +księdzem|księ=dzem|18 +księżyc|księ=życ|18 +mojego|mo=je=go|18 +mruknęła|mruk=nę=ła|18 +musieli|mu=sie=li|18 +młynie|mły=nie|18 +należy|na=le=ży|18 +nieprawda|nie=praw=da|18 +obaczysz|oba=czysz|18 +oczymgnienie|oczym=gnie=nie|18 +odszedł|od=szedł|18 +ogarniał|ogar=niał|18 +otwarte|otwar=te|18 +papier|pa=pier|18 +patrzyła|pa=trzy=ła|18 +pierwszej|pierw=szej|18 +pierzyną|pie=rzy=ną|18 +pieśnie|pie=śnie|18 +pisarz|pi=sarz|18 +plecach|ple=cach|18 +poczuł|po=czuł|18 +Podlesie|Pod=le=sie|18 +Podlesiu|Pod=le=siu|18 +podniosła|pod=nio=sła|18 +pogadywał|po=ga=dy=wał|18 +pokornie|po=kor=nie|18 +powróci|po=wró=ci|18 +południu|po=łu=dniu|18 +progiem|pro=giem|18 +prosić|pro=sić|18 +Przecież|Prze=cież|18 +przejął|prze=jął|18 +przyjść|przyjść|18 +przysiadła|przy=sia=dła|18 +ptaszek|pta=szek|18 +pytała|py=ta=ła|18 +robiło|ro=bi=ło|18 +rzadka|rzad=ka|18 +rękami|rę=ka=mi|18 +skamlał|skam=lał|18 +stworzenie|stwo=rze=nie|18 +swoimi|swo=imi|18 +szczekał|szcze=kał|18 +szepty|szep=ty|18 +słuchali|słu=cha=li|18 +słyszała|sły=sza=ła|18 +trząsł|trząsł|18 +uciekła|ucie=kła|18 +uczynił|uczy=nił|18 +wesela|we=se=la|18 +wełniaki|weł=nia=ki|18 +widziały|wi=dzia=ły|18 +wielgachne|wiel=gach=ne|18 +wielkie|wiel=kie|18 +wlekły|wle=kły|18 +wyszło|wy=szło|18 +wzieni|wzie=ni|18 +zapomniał|za=po=mniał|18 +zapytała|za=py=ta=ła|18 +ziemiach|zie=miach|18 +zrywał|zry=wał|18 +ścierwy|ścier=wy|18 +środkiem|środ=kiem|18 +świece|świe=ce|18 +świętym|świę=tym|18 +żywego|ży=we=go|18 +bezwiednie|bez=wied=nie|17 +Borynowej|Bo=ry=no=wej|17 +Borynę|Bo=ry=nę|17 +Będzie|Bę=dzie|17 +chustkę|chust=kę|17 +chwilami|chwi=la=mi|17 +cisnąc|ci=snąc|17 +czekając|cze=ka=jąc|17 +dalejże|da=lej=że|17 +daruję|da=ru=ję|17 +dodała|do=da=ła|17 +dziecińskie|dzie=ciń=skie|17 +Dziedzic|Dzie=dzic|17 +dziwną|dziw=ną|17 +frasobliwie|fra=so=bli=wie|17 +gardziel|gar=dziel|17 +gardzieli|gar=dzie=li|17 +gospodynie|go=spo=dy=nie|17 +gromadzie|gro=ma=dzie|17 +huczał|hu=czał|17 +jarmark|jar=mark|17 +jesteście|je=ste=ście|17 +kamieniem|ka=mie=niem|17 +karczmę|karcz=mę|17 +kryjomo|kry=jo=mo|17 +krzykał|krzy=kał|17 +krótkie|krót=kie|17 +księży|księ=ży|17 +kłótni|kłót=ni|17 +ludzkie|ludz=kie|17 +mamrotał|mam=ro=tał|17 +myśląc|my=śląc|17 +najgorsze|naj=gor=sze|17 +niecierpliwie|nie=cier=pli=wie|17 +niełacno|nie=łac=no|17 +nijakiej|ni=ja=kiej|17 +ochotnie|ochot=nie|17 +ostanie|osta=nie|17 +ostatku|ostat=ku|17 +ołtarzem|oł=ta=rzem|17 +padały|pa=da=ły|17 +plebanii|ple=ba=nii|17 +podniesły|pod=nie=sły|17 +pognał|po=gnał|17 +poredzę|po=re=dzę|17 +posobnie|po=sob=nie|17 +poważnie|po=waż=nie|17 +powiadają|po=wia=da=ją|17 +Przeciek|Prze=ciek|17 +przeszło|prze=szło|17 +przykład|przy=kład|17 +rozeznał|ro=ze=znał|17 +ruszyć|ru=szyć|17 +skończyli|skoń=czy=li|17 +spoglądał|spo=glą=dał|17 +spozierając|spo=zie=ra=jąc|17 +sprawiedliwość|spra=wie=dli=wość|17 +stanęło|sta=nę=ło|17 +szczęścia|szczę=ścia|17 +słomiane|sło=mia=ne|17 +topolową|to=po=lo=wą|17 +trudem|tru=dem|17 +trwogą|trwo=gą|17 +urodna|urod=na|17 +Wieczór|Wie=czór|17 +wielkiej|wiel=kiej|17 +wisiały|wi=sia=ły|17 +wolniej|wol=niej|17 +wrzeszczała|wrzesz=cza=ła|17 +wszelkie|wszel=kie|17 +wszystkiej|wszyst=kiej|17 +wyciągał|wy=cią=gał|17 +wynosiły|wy=no=si=ły|17 +wyrzekła|wy=rze=kła|17 +wzdłuż|wzdłuż|17 +zaklął|za=klął|17 +zaśmiała|za=śmia=ła|17 +zbierać|zbie=rać|17 +zbierał|zbie=rał|17 +zbrakło|zbra=kło|17 +zielonych|zie=lo=nych|17 +śniadaniu|śnia=da=niu|17 +światłem|świa=tłem|17 +świeciły|świe=ci=ły|17 +żałości|ża=ło=ści|17 +ambony|am=bo=ny|16 +bezradnie|bez=rad=nie|16 +biedota|bie=do=ta|16 +Boryną|Bo=ry=ną|16 +broniła|bro=ni=ła|16 +bywało|by=wa=ło|16 +chłopi|chło=pi|16 +ciągnęli|cią=gnę=li|16 +czerwoną|czer=wo=ną|16 +człowieku|czło=wie=ku|16 +dojrzała|doj=rza=ła|16 +domowi|do=mo=wi|16 +długim|dłu=gim|16 +gospodarstwo|go=spo=dar=stwo|16 +granie|gra=nie|16 +grzechu|grze=chu|16 +gwałtownie|gwał=tow=nie|16 +głębokim|głę=bo=kim|16 +jakiej|ja=kiej|16 +kamieniami|ka=mie=nia=mi|16 +karczma|karcz=ma|16 +kałuże|ka=łu=że|16 +kochanie|ko=cha=nie|16 +kreminału|kre=mi=na=łu|16 +którymi|któ=ry=mi|16 +kwiaty|kwia=ty|16 +markotno|mar=kot=no|16 +Modlicy|Mo=dli=cy|16 +modlitwy|mo=dli=twy|16 +niebądź|nie=bądź|16 +odeszła|ode=szła|16 +opadły|opa=dły|16 +opowiadać|opo=wia=dać|16 +ostanę|osta=nę|16 +ostatniej|ostat=niej|16 +ołtarza|oł=ta=rza|16 +parobka|pa=rob=ka|16 +parobki|pa=rob=ki|16 +pierwszego|pierw=sze=go|16 +pierzyny|pie=rzy=ny|16 +pilnować|pil=no=wać|16 +pięknie|pięk=nie|16 +postawił|po=sta=wił|16 +pościeli|po=ście=li|16 +przeciągał|prze=cią=gał|16 +przyjacielstwa|przy=ja=ciel=stwa|16 +przyjdą|przyj=dą|16 +przyniesła|przy=nie=sła|16 +płachtą|płach=tą|16 +płótno|płót=no|16 +rankiem|ran=kiem|16 +rozmysłem|roz=my=słem|16 +rozumiesz|ro=zu=miesz|16 +ruszać|ru=szać|16 +siedziały|sie=dzia=ły|16 +sielny|siel=ny|16 +spoglądając|spo=glą=da=jąc|16 +stojała|sto=ja=ła|16 +strażników|straż=ni=ków|16 +szkołę|szko=łę|16 +trwogi|trwo=gi|16 +tłumaczył|tłu=ma=czył|16 +wiedli|wie=dli|16 +wodząc|wo=dząc|16 +wrzeszczał|wrzesz=czał|16 +wróble|wró=ble|16 +wychodzili|wy=cho=dzi=li|16 +wzięły|wzię=ły|16 +zachodu|za=cho=du|16 +zagadnął|za=gad=nął|16 +zapachy|za=pa=chy|16 +zatrząsł|za=trząsł|16 +zauważył|za=uwa=żył|16 +znaczenie|zna=cze=nie|16 +zważał|zwa=żał|16 +śpiewy|śpie=wy|16 +świtaniu|świ=ta=niu|16 +bierąc|bie=rąc|15 +boskiej|bo=skiej|15 +chałupa|cha=łu=pa|15 +chmura|chmu=ra|15 +Chodźcie|Chodź=cie|15 +chłopu|chło=pu|15 +ciemności|ciem=no=ści|15 +ciężkim|cięż=kim|15 +cudeńka|cu=deń=ka|15 +czekają|cze=ka=ją|15 +czekała|cze=ka=ła|15 +czysta|czy=sta|15 +dobrej|do=brej|15 +dobrością|do=bro=ścią|15 +dostał|do=stał|15 +drzewem|drze=wem|15 +drzwiach|drzwiach|15 +dzieuch|dzieuch|15 +dzieucha|dzieu=cha|15 +gankiem|gan=kiem|15 +gałęziami|ga=łę=zia=mi|15 +grdykę|grdy=kę|15 +grzechy|grze=chy|15 +Jagustynką|Ja=gu=styn=ką|15 +kapustę|ka=pu=stę|15 +kowalem|ko=wa=lem|15 +kromie|kro=mie|15 +krzepko|krzep=ko|15 +kulasami|ku=la=sa=mi|15 +kwiatów|kwia=tów|15 +Laboga|La=bo=ga|15 +laczego|la=cze=go|15 +maciorę|ma=cio=rę|15 +miarkując|miar=ku=jąc|15 +miedzach|mie=dzach|15 +milczenie|mil=cze=nie|15 +Muzyka|Mu=zy=ka|15 +najlepiej|naj=le=piej|15 +napity|na=pi=ty|15 +niedziele|nie=dzie=le|15 +niedługo|nie=dłu=go|15 +Niemcy|Niem=cy|15 +nigdzie|ni=g=dzie|15 +obejścia|obej=ścia|15 +odpust|od=pust|15 +ozorem|ozo=rem|15 +pacierzem|pa=cie=rzem|15 +palcami|pal=ca=mi|15 +patrzą|pa=trzą|15 +piaskiem|pia=skiem|15 +piersiach|pier=siach|15 +pociągał|po=cią=gał|15 +pogadywali|po=ga=dy=wa=li|15 +pokazał|po=ka=zał|15 +pomagał|po=ma=gał|15 +pomogło|po=mo=gło|15 +pomoże|po=mo=że|15 +porządnie|po=rząd=nie|15 +powiedają|po=wie=da=ją|15 +powiódł|po=wiódł|15 +profit|pro=fit|15 +przeciwić|prze=ciw=ić|15 +przejdzie|przej=dzie|15 +przeszła|prze=szła|15 +przyjdę|przyj=dę|15 +przykazał|przy=ka=zał|15 +przędła|przę=dła|15 +próżnicy|próż=ni=cy|15 +pustych|pu=stych|15 +północka|pół=noc=ka|15 +płaczu|pła=czu|15 +płakać|pła=kać|15 +płakała|pła=ka=ła|15 +płotami|pło=ta=mi|15 +radził|ra=dził|15 +ramionami|ra=mio=na=mi|15 +rwetes|rwe=tes|15 +rzekłem|rze=kłem|15 +sierota|sie=ro=ta|15 +sieroty|sie=ro=ty|15 +skończył|skoń=czył|15 +składowano|skła=do=wa=no|15 +spojrzała|spoj=rza=ła|15 +stając|sta=jąc|15 +staremu|sta=re=mu|15 +starsze|star=sze|15 +stojały|sto=ja=ły|15 +surowo|su=ro=wo|15 +słupach|słu=pach|15 +takiemu|ta=kie=mu|15 +tartaku|tar=ta=ku|15 +uciekł|uciekł|15 +warknęła|wark=nę=ła|15 +wełniak|weł=niak|15 +wiatru|wia=tru|15 +wpatrzony|wpa=trzo=ny|15 +wrzawa|wrza=wa|15 +wychodziła|wy=cho=dzi=ła|15 +wyciągać|wy=cią=gać|15 +wysokie|wy=so=kie|15 +zabawiali|za=ba=wia=li|15 +zabawy|za=ba=wy|15 +zabrała|za=bra=ła|15 +zachodzie|za=cho=dzie|15 +zadaszenie|za=da=sze=nie|15 +zaglądała|za=glą=da=ła|15 +zaprzeszłego|za=prze=szłe=go|15 +zdążył|zdą=żył|15 +zmiłowania|zmi=ło=wa=nia|15 +zwyczaju|zwy=cza=ju|15 +środka|środ=ka|15 +święto|świę=to|15 +alkierza|al=kie=rza|14 +baczenie|ba=cze=nie|14 +baczył|ba=czył|14 +Balcerkowa|Bal=cer=ko=wa|14 +białymi|bia=ły=mi|14 +bojała|bo=ja=ła|14 +brzega|brze=ga|14 +całych|ca=łych|14 +całymi|ca=ły=mi|14 +chodzą|cho=dzą|14 +chwiały|chwia=ły|14 +chłopakami|chło=pa=ka=mi|14 +ciekawie|cie=ka=wie|14 +ciołek|cio=łek|14 +cisnęła|ci=snę=ła|14 +cisnęły|ci=snę=ły|14 +czekali|cze=ka=li|14 +czynić|czy=nić|14 +człowiekowi|czło=wie=ko=wi|14 +dachów|da=chów|14 +dlaczego|dla=cze=go|14 +drogami|dro=ga=mi|14 +dziady|dzia=dy|14 +galanty|ga=lan=ty|14 +godnie|god=nie|14 +gorsze|gor=sze|14 +Gospodarz|Go=spo=darz|14 +gospodynią|go=spo=dy=nią|14 +gotowe|go=to=we|14 +groszy|gro=szy|14 +górnie|gór=nie|14 +gęściej|gę=ściej|14 +głodem|gło=dem|14 +innych|in=nych|14 +Jagusine|Ja=gu=si=ne|14 +jesienią|je=sie=nią|14 +jesteś|je=steś|14 +kapusty|ka=pu=sty|14 +kilkanaście|kil=ka=na=ście|14 +kobietę|ko=bie=tę|14 +krzywd|krzywd|14 +któremu|któ=re=mu|14 +kurzawa|ku=rza=wa|14 +kłótnie|kłót=nie|14 +lepsze|lep=sze|14 +liście|li=ście|14 +Loboga|Lo=bo=ga|14 +Miemcy|Miem=cy|14 +mieście|mie=ście|14 +modlił|mo=dlił|14 +mądrze|mą=drze|14 +młodych|mło=dych|14 +najbardziej|naj=bar=dziej|14 +narodowi|na=ro=do=wi|14 +Nastusi|Na=stu=si|14 +nauczał|na=uczał|14 +nawrotem|na=wro=tem|14 +niejedna|nie=jed=na|14 +niektóre|nie=któ=re|14 +nieprzytomny|nie=przy=tom=ny|14 +niesła|nie=sła|14 +odwieczerzu|od=wie=cze=rzu|14 +ogarnęła|ogar=nę=ła|14 +oglądając|oglą=da=jąc|14 +oglądał|oglą=dał|14 +oparte|opar=te|14 +ostały|osta=ły|14 +otworzył|otwo=rzył|14 +pięście|pię=ście|14 +pociekły|po=cie=kły|14 +podobno|po=dob=no|14 +pomyślał|po=my=ślał|14 +poniósł|po=niósł|14 +poruszył|po=ru=szył|14 +powietrza|po=wie=trza|14 +prawdzie|praw=dzie|14 +przebierał|prze=bie=rał|14 +przednówek|przed=nó=wek|14 +przestał|prze=stał|14 +prześmiechem|prze=śmie=chem|14 +przychodził|przy=cho=dził|14 +przycichło|przy=ci=chło|14 +przyszły|przy=szły|14 +puszczał|pusz=czał|14 +radzili|ra=dzi=li|14 +rozmyślał|roz=my=ślał|14 +rozpowiadać|roz=po=wia=dać|14 +ruszyła|ru=szy=ła|14 +ryknął|ryk=nął|14 +rzetelnie|rze=tel=nie|14 +rzewliwie|rzew=li=wie|14 +rzucić|rzu=cić|14 +senność|sen=ność|14 +siadła|sia=dła|14 +splunął|splu=nął|14 +spojrzenia|spoj=rze=nia|14 +społem|spo=łem|14 +sprzed|sprzed|14 +spytał|spy=tał|14 +srogie|sro=gie|14 +starczy|star=czy|14 +statki|stat=ki|14 +strasznym|strasz=nym|14 +szczęście|szczę=ście|14 +szczęśliwości|szczę=śli=wo=ści|14 +szkody|szko=dy|14 +słodko|słod=ko|14 +tamten|tam=ten|14 +toczyły|to=czy=ły|14 +tygodnie|ty=go=dnie|14 +tłukła|tłu=kła|14 +uciekał|ucie=kał|14 +utrudzenia|utru=dze=nia|14 +wiatrem|wia=trem|14 +wieczorami|wie=czo=ra=mi|14 +wielką|wiel=ką|14 +wlekła|wle=kła|14 +wpadła|wpa=dła|14 +wrychle|wry=chle|14 +wróciła|wró=ci=ła|14 +Wszyscy|Wszy=scy|14 +wszystką|wszyst=ką|14 +wychodzić|wy=cho=dzić|14 +wyjścia|wyj=ścia|14 +wykrzykiwał|wy=krzy=ki=wał|14 +zakręcił|za=krę=cił|14 +zapach|za=pach|14 +zapatrzona|za=pa=trzo=na|14 +zapomniała|za=po=mnia=ła|14 +zapraszał|za=pra=szał|14 +zawrócił|za=wró=cił|14 +zaśpiewały|za=śpie=wa=ły|14 +zbytnio|zbyt=nio|14 +ziarna|ziar=na|14 +zjawił|zja=wił|14 +zmiłowanie|zmi=ło=wa=nie|14 +zrobię|zro=bię|14 +zupełnie|zu=peł=nie|14 +ścierwo|ścier=wo|14 +śmiechów|śmie=chów|14 +śmiejąc|śmie=jąc|14 +święconą|świę=co=ną|14 +żeście|że=ście|14 +baczyć|ba=czyć|13 +bezustannie|bez=u=stan=nie|13 +biorąc|bio=rąc|13 +brzuch|brzuch|13 +buchnęły|buch=nę=ły|13 +bójcie|bój=cie|13 +ciasno|cia=sno|13 +ciemno|ciem=no|13 +cieniów|cie=niów|13 +ciągnęła|cią=gnę=ła|13 +ciągnęło|cią=gnę=ło|13 +ciężej|cię=żej|13 +ciężka|cięż=ka|13 +cofnął|cof=nął|13 +czerniały|czer=nia=ły|13 +czerwieniały|czer=wie=nia=ły|13 +czyniąc|czy=niąc|13 +dobrości|do=bro=ści|13 +drugiemu|dru=gie=mu|13 +dworem|dwo=rem|13 +Dzieci|Dzie=ci|13 +dziesiątkę|dzie=siąt=kę|13 +dziwno|dziw=no|13 +godzina|go=dzi=na|13 +gorzałka|go=rzał=ka|13 +gorzałką|go=rzał=ką|13 +gorące|go=rą=ce|13 +gospodarstwie|go=spo=dar=stwie|13 +grzbiet|grzbiet|13 +gęstwą|gę=stwą|13 +huczały|hu=cza=ły|13 +kijami|ki=ja=mi|13 +kolację|ko=la=cję|13 +koniom|ko=niom|13 +krzyczy|krzy=czy|13 +krótki|krót=ki|13 +książce|książ=ce|13 +kwiatem|kwia=tem|13 +lipeckich|li=pec=kich|13 +miłosierny|mi=ło=sier=ny|13 +mroków|mro=ków|13 +muzyki|mu=zy=ki|13 +najlepsze|naj=lep=sze|13 +namiętnie|na=mięt=nie|13 +nasienie|na=sie=nie|13 +niechętnie|nie=chęt=nie|13 +niedziela|nie=dzie=la|13 +niespodzianie|nie=spo=dzia=nie|13 +niestrudzenie|nie=stru=dze=nie|13 +niewymłócone|nie=wy=młó=co=ne|13 +niezgorszy|nie=zgor=szy|13 +niskie|ni=skie|13 +nocami|no=ca=mi|13 +nowina|no=wi=na|13 +obejściach|obej=ściach|13 +obejściu|obej=ściu|13 +oborze|obo=rze|13 +obrazów|ob=ra=zów|13 +odchodnym|od=chod=nym|13 +oderwać|ode=rwać|13 +odparła|od=par=ła|13 +ogromnym|ogrom=nym|13 +ogromną|ogrom=ną|13 +ojciec|oj=ciec|13 +ojcowe|oj=co=we|13 +oknach|oknach|13 +parobcy|pa=rob=cy|13 +Pańskiego|Pań=skie=go|13 +piekła|pie=kła|13 +piersiami|pier=sia=mi|13 +pochowek|po=cho=wek|13 +poganiał|po=ga=niał|13 +pomyśleć|po=my=śleć|13 +popadło|po=pa=dło|13 +poredzając|po=re=dza=jąc|13 +porwała|po=rwa=ła|13 +porządki|po=rząd=ki|13 +porządku|po=rząd=ku|13 +posypały|po=sy=pa=ły|13 +powlókł|po=wlókł|13 +przejęty|prze=ję=ty|13 +przyjechał|przy=je=chał|13 +przyodziewy|przy=odzie=wy|13 +przypiekało|przy=pie=ka=ło|13 +puścili|pu=ści=li|13 +puściła|pu=ści=ła|13 +płakał|pła=kał|13 +płotach|pło=tach|13 +płynęły|pły=nę=ły|13 +Rochem|Ro=chem|13 +rozeszli|ro=ze=szli|13 +rozlegało|roz=le=ga=ło|13 +rozmyślać|roz=my=ślać|13 +rozumiejąc|ro=zu=mie=jąc|13 +rzekli|rze=kli|13 +różnie|róż=nie|13 +samemu|sa=me=mu|13 +sennie|sen=nie|13 +Siadajcie|Sia=daj=cie|13 +siedzieć|sie=dzieć|13 +silnie|sil=nie|13 +skończy|skoń=czy|13 +smutek|smu=tek|13 +spieka|spie=ka|13 +sposobu|spo=so=bu|13 +spotkał|spo=tkał|13 +stadami|sta=da=mi|13 +strachliwie|stra=chli=wie|13 +straszne|strasz=ne|13 +strugi|stru=gi|13 +szerokie|sze=ro=kie|13 +Szymka|Szym=ka|13 +słoniny|sło=ni=ny|13 +słuchała|słu=cha=ła|13 +tartak|tar=tak|13 +trocha|tro=cha|13 +turkotał|tur=ko=tał|13 +twarzą|twa=rzą|13 +tysiące|ty=sią=ce|13 +tłumaczyła|tłu=ma=czy=ła|13 +waliła|wa=li=ła|13 +weselem|we=se=lem|13 +wiedzieli|wie=dzie=li|13 +wieprzka|wie=prz=ka|13 +wschodu|wscho=du|13 +wybuchnął|wy=buch=nął|13 +wzrokiem|wzro=kiem|13 +zaczęło|za=czę=ło|13 +zapamiętał|za=pa=mię=tał|13 +zauważyła|za=uwa=ży=ła|13 +zazdrość|za=zdrość|13 +ziarno|ziar=no|13 +zwiesny|zwie=sny|13 +zwłaszcza|zwłasz=cza|13 +zębach|zę=bach|13 +zębami|zę=ba=mi|13 +złodziej|zło=dziej|13 +złotówkę|zło=tów=kę|13 +złoście|zło=ście|13 +łakomie|ła=ko=mie|13 +ścierwa|ścier=wa|13 +śpiewać|śpie=wać|13 +światy|świa=ty|13 +świąteczne|świą=tecz=ne|13 +świętych|świę=tych|13 +żelazo|że=la=zo|13 +barłogu|bar=ło=gu|12 +beczki|becz=ki|12 +bełkotał|beł=ko=tał|12 +biedro|bie=dro|12 +będziecie|bę=dzie=cie|12 +będziemy|bę=dzie=my|12 +chcesz|chcesz|12 +chodzić|cho=dzić|12 +chytrze|chy=trze|12 +Chłopi|Chło=pi|12 +ciemnościach|ciem=no=ściach|12 +cisnął|ci=snął|12 +ciężkiej|cięż=kiej|12 +czując|czu=jąc|12 +Częstochowy|Czę=sto=cho=wy|12 +częstował|czę=sto=wał|12 +Człowiek|Czło=wiek|12 +dalekich|da=le=kich|12 +deszczu|desz=czu|12 +Dobrodziej|Do=bro=dziej|12 +domami|do=ma=mi|12 +dworowi|dwo=ro=wi|12 +dworskich|dwor=skich|12 +dziewczyn|dziew=czyn|12 +dziewka|dziew=ka|12 +dziewki|dziew=ki|12 +godzinie|go=dzi=nie|12 +gorąca|go=rą=ca|12 +gospodarzu|go=spo=da=rzu|12 +gwiazdami|gwiaz=da=mi|12 +głodny|głod=ny|12 +głucha|głu=cha|12 +głęboka|głę=bo=ka|12 +Idźcie|Idź=cie|12 +inwentarz|in=wen=tarz|12 +Jagata|Ja=ga=ta|12 +jękiem|ję=kiem|12 +kijaszkiem|ki=jasz=kiem|12 +komornice|ko=mor=ni=ce|12 +komorników|ko=mor=ni=ków|12 +koniczyny|ko=ni=czy=ny|12 +Kozioł|Ko=zioł|12 +krowie|kro=wie|12 +krwawe|krwa=we|12 +krzyczeli|krzy=cze=li|12 +krzywą|krzy=wą|12 +księdzu|księ=dzu|12 +kupować|ku=po=wać|12 +kurzawie|ku=rza=wie|12 +Lektur|Lek=tur|12 +Lektury|Lek=tu=ry|12 +ludzką|ludz=ką|12 +lśniły|lśni=ły|12 +mgnienie|mgnie=nie|12 +miedzami|mie=dza=mi|12 +Miemców|Miem=ców|12 +myśleć|my=śleć|12 +nagrzane|na=grza=ne|12 +najgłośniej|naj=gło=śniej|12 +najpierwsze|naj=pierw=sze|12 +Nastki|Na=st=ki|12 +niekajś|nie=kajś|12 +niezgorsze|nie=zgor=sze|12 +nozdrzach|noz=drzach|12 +obrządków|ob=rząd=ków|12 +oczywiście|oczy=wi=ście|12 +odmianę|od=mia=nę|12 +odpowiadała|od=po=wia=da=ła|12 +odpuście|od=pu=ście|12 +odwracając|od=wra=ca=jąc|12 +ogarniała|ogar=nia=ła|12 +okazji|oka=zji|12 +opłotków|opłot=ków|12 +orzydle|orzy=dle|12 +ostali|osta=li|12 +oszalałe|osza=la=łe|12 +otwarty|otwar=ty|12 +ołtarz|oł=tarz|12 +pacierzów|pa=cie=rzów|12 +paliły|pa=li=ły|12 +parobkami|pa=rob=ka=mi|12 +pierwszym|pierw=szym|12 +piorun|pio=run|12 +plecami|ple=ca=mi|12 +pochylił|po=chy=lił|12 +pochylone|po=chy=lo=ne|12 +pociągnął|po=cią=gnął|12 +poczęły|po=czę=ły|12 +podziwem|po=dzi=wem|12 +podłogę|pod=ło=gę|12 +pojechali|po=je=cha=li|12 +ponosiło|po=no=si=ło|12 +portkach|por=t=kach|12 +porządny|po=rząd=ny|12 +poszło|po=szło|12 +powiał|po=wiał|12 +powiedzą|po=wie=dzą|12 +powoli|po=wo=li|12 +powracał|po=wra=cał|12 +powrotu|po=wro=tu|12 +powstając|po=wsta=jąc|12 +położyła|po=ło=ży=ła|12 +pościele|po=ście=le|12 +prawić|pra=wić|12 +przeciwko|prze=ciw=ko|12 +przejść|przejść|12 +przeróżne|prze=róż=ne|12 +przychodziło|przy=cho=dzi=ło|12 +przychodziły|przy=cho=dzi=ły|12 +przygięte|przy=gię=te|12 +przyleciała|przy=le=cia=ła|12 +przystanęła|przy=sta=nę=ła|12 +ptactwo|ptac=two|12 +rozchodzić|roz=cho=dzić|12 +rozpowiadał|roz=po=wia=dał|12 +rozważał|roz=wa=żał|12 +skowronki|skow=ron=ki|12 +skręcił|skrę=cił|12 +spokoju|spo=ko=ju|12 +społecznie|spo=łecz=nie|12 +sprzedał|sprze=dał|12 +stanęły|sta=nę=ły|12 +stronami|stro=na=mi|12 +sygnaturka|sy=gna=tur=ka|12 +szykować|szy=ko=wać|12 +sąsiadów|są=sia=dów|12 +słodkim|słod=kim|12 +topole|to=po=le|12 +trzaskał|trza=skał|12 +twarzach|twa=rzach|12 +uderzył|ude=rzył|12 +uśmiechem|uśmie=chem|12 +wesołe|we=so=łe|12 +wespół|we=spół|12 +Wiater|Wia=ter|12 +wichry|wi=chry|12 +widzisz|wi=dzisz|12 +Widzisz|Wi=dzisz|12 +wieczerzy|wie=cze=rzy|12 +winowaty|wi=no=wa=ty|12 +większą|więk=szą|12 +Wolnych|Wol=nych|12 +wołali|wo=ła=li|12 +wrzasków|wrza=sków|12 +wrzało|wrza=ło|12 +wstawał|wsta=wał|12 +wstydu|wsty=du|12 +wyglądał|wy=glą=dał|12 +wyjdzie|wyj=dzie|12 +wyniesło|wy=nie=sło|12 +wynieśli|wy=nie=śli|12 +wyrozumieć|wy=ro=zu=mieć|12 +wyrwać|wy=rwać|12 +wyrzekał|wy=rze=kał|12 +wyrzekł|wy=rzekł|12 +wytrzymania|wy=trzy=ma=nia|12 +wywarła|wy=war=ła|12 +wzmagał|wzma=gał|12 +zabierając|za=bie=ra=jąc|12 +zachód|za=chód|12 +zagadywał|za=ga=dy=wał|12 +zagona|za=go=na|12 +zapadał|za=pa=dał|12 +zapaską|za=pa=ską|12 +zawziętość|za=wzię=tość|12 +zgrozy|zgro=zy|12 +zmilkła|zmil=kła|12 +zrobili|zro=bi=li|12 +zrozumieć|zro=zu=mieć|12 +zwalił|zwa=lił|12 +zwaliła|zwa=li=ła|12 +złapać|zła=pać|12 +złodzieje|zło=dzie=je|12 +śniadania|śnia=da=nia|12 +śniadanie|śnia=da=nie|12 +śpiewania|śpie=wa=nia|12 +świateł|świa=teł|12 +żałosne|ża=ło=sne|12 +Antków|Ant=ków|11 +barany|ba=ra=ny|11 +Borynowe|Bo=ry=no=we|11 +Bożego|Bo=że=go|11 +brakło|bra=kło|11 +brogiem|bro=giem|11 +brzaski|brza=ski|11 +bystro|by=stro|11 +całując|ca=łu=jąc|11 +chorągwie|cho=rą=gwie|11 +chwycił|chwy=cił|11 +chwytał|chwy=tał|11 +Chłopy|Chło=py|11 +Cichojta|Ci=choj=ta|11 +ciemna|ciem=na|11 +ciepnąć|ciep=nąć|11 +ciężarem|cię=ża=rem|11 +czerwieniły|czer=wie=ni=ły|11 +czystą|czy=stą|11 +dawała|da=wa=ła|11 +dawnego|daw=ne=go|11 +dobroci|do=bro=ci|11 +dochodził|do=cho=dził|11 +dorzuciła|do=rzu=ci=ła|11 +dygotał|dy=go=tał|11 +dziewucha|dzie=wu=cha|11 +elegancko|ele=ganc=ko|11 +gniewem|gnie=wem|11 +gniewu|gnie=wu|11 +gospodarka|go=spo=dar=ka|11 +gospodarza|go=spo=da=rza|11 +gruchnęła|gruch=nę=ła|11 +gąszczach|gąsz=czach|11 +głosami|gło=sa=mi|11 +głęboką|głę=bo=ką|11 +jajków|jaj=ków|11 +jakich|ja=kich|11 +jarzyły|ja=rzy=ły|11 +jechać|je=chać|11 +jesieni|je=sie=ni|11 +jękliwie|ję=kli=wie|11 +jęknął|jęk=nął|11 +karczmą|karcz=mą|11 +kazała|ka=za=ła|11 +kałdun|kał=dun|11 +kobietom|ko=bie=tom|11 +kochania|ko=cha=nia|11 +korale|ko=ra=le|11 +kożuchem|ko=żu=chem|11 +krzepki|krzep=ki|11 +krzyża|krzy=ża|11 +księże|księ=że|11 +kwardy|kwar=dy|11 +kwiatami|kwia=ta=mi|11 +kładły|kła=dły|11 +Kłębowej|Kłę=bo=wej|11 +ludzki|ludz=ki|11 +Macieja|Ma=cie=ja|11 +martwe|mar=twe|11 +Marysia|Ma=ry=sia|11 +medytował|me=dy=to=wał|11 +miarkować|miar=ko=wać|11 +miejscach|miej=scach|11 +modlitwę|mo=dli=twę|11 +morzył|mo=rzył|11 +możecie|mo=że=cie|11 +musiało|mu=sia=ło|11 +mówicie|mó=wi=cie|11 +młynowi|mły=no=wi|11 +najwięcej|naj=wię=cej|11 +naleźć|na=leźć|11 +naszego|na=sze=go|11 +niechcenia|nie=chce=nia|11 +niedaleko|nie=da=le=ko|11 +niemały|nie=ma=ły|11 +nieprzytomnie|nie=przy=tom=nie|11 +niewiela|nie=wie=la|11 +niewolił|nie=wo=lił|11 +nowinę|no=wi=nę|11 +obchodzić|ob=cho=dzić|11 +obcierając|ob=cie=ra=jąc|11 +obiadu|obia=du|11 +obzierając|obzie=ra=jąc|11 +odpoczywać|od=po=czy=wać|11 +odpowiedzi|od=po=wie=dzi|11 +odrobek|od=ro=bek|11 +odwróciła|od=wró=ci=ła|11 +ogniach|ogniach|11 +opowiadał|opo=wia=dał|11 +opowiadała|opo=wia=da=ła|11 +palcach|pal=cach|11 +pamiętać|pa=mię=tać|11 +pastwisk|pa=stwisk|11 +pazuchę|pa=zu=chę|11 +Pański|Pań=ski|11 +Pewnikiem|Pew=ni=kiem|11 +pilnował|pil=no=wał|11 +pluchy|plu=chy|11 +pobożne|po=boż=ne|11 +pobożnie|po=boż=nie|11 +począł|po=czął|11 +poglądał|po=glą=dał|11 +pogwary|po=gwa=ry|11 +pokoje|po=ko=je|11 +pomiarkował|po=miar=ko=wał|11 +ponieść|po=nieść|11 +poradzę|po=ra=dzę|11 +porządek|po=rzą=dek|11 +posłyszał|po=sły=szał|11 +powiedali|po=wie=da=li|11 +powiedzieli|po=wie=dzie=li|11 +powróciła|po=wró=ci=ła|11 +pozwoli|po=zwo=li|11 +połednie|po=łed=nie|11 +prawdy|praw=dy|11 +Proboszcz|Pro=boszcz|11 +przedzwonili|prze=dzwo=ni=li|11 +przemówić|prze=mó=wić|11 +przepijali|prze=pi=ja=li|11 +przeszłym|prze=szłym|11 +przynaglał|przy=na=glał|11 +przypomnienie|przy=po=mnie=nie|11 +przytajone|przy=ta=jo=ne|11 +przywarła|przy=war=ła|11 +przyzbie|przy=zbie|11 +ptaszki|ptasz=ki|11 +puścić|pu=ścić|11 +pójdziemy|pój=dzie=my|11 +płachty|płach=ty|11 +płachtę|płach=tę|11 +płaczów|pła=czów|11 +radosnym|ra=do=snym|11 +rajcowały|raj=co=wa=ły|11 +raźniej|raź=niej|11 +robotą|ro=bo=tą|11 +rodzone|ro=dzo=ne|11 +rodzony|ro=dzo=ny|11 +rolach|ro=lach|11 +rowach|ro=wach|11 +rozcież|roz=cież|11 +rozlewały|roz=le=wa=ły|11 +rozniesło|roz=nie=sło|11 +Rychtyk|Rych=tyk|11 +rzucając|rzu=ca=jąc|11 +rzucały|rzu=ca=ły|11 +rzędem|rzę=dem|11 +różnoście|róż=no=ście|11 +siedząc|sie=dząc|11 +sinych|si=nych|11 +skarżyła|skar=ży=ła|11 +skrzydła|skrzy=dła|11 +skrzypicy|skrzy=pi=cy|11 +skrzypki|skrzyp=ki|11 +skwapnie|skwap=nie|11 +sołtysem|soł=ty=sem|11 +spiekota|spie=ko=ta|11 +spodoba|spodo=ba|11 +sprawą|spra=wą|11 +starszym|star=szym|11 +stawała|sta=wa=ła|11 +stawiał|sta=wiał|11 +stodołę|sto=do=łę|11 +straszną|strasz=ną|11 +swarów|swa=rów|11 +sypały|sy=pa=ły|11 +Szymon|Szy=mon|11 +szynkwasie|szynk=wa=sie|11 +sądzie|są=dzie|11 +sąsiedzku|są=siedz=ku|11 +słodki|słod=ki|11 +tańcował|tań=co=wał|11 +trzeci|trze=ci|11 +trzymać|trzy=mać|11 +trzymała|trzy=ma=ła|11 +turbacja|tur=ba=cja|11 +turkot|tur=kot|11 +tymczasem|tym=cza=sem|11 +tłukły|tłu=kły|11 +urządzenie|urzą=dze=nie|11 +uspokajać|uspo=ka=jać|11 +wargach|war=gach|11 +wchodząc|wcho=dząc|11 +weselu|we=se=lu|11 +weszła|we=szła|11 +wichura|wi=chu=ra|11 +wielka|wiel=ka|11 +wierzcie|wierz=cie|11 +winien|wi=nien|11 +wrótnie|wrót=nie|11 +wsparłszy|wsparł=szy|11 +wspominki|wspo=min=ki|11 +wstęgi|wstę=gi|11 +wybrał|wy=brał|11 +wygnał|wy=gnał|11 +wyrywał|wy=ry=wał|11 +wyszli|wy=szli|11 +wzdychali|wzdy=cha=li|11 +wzgardliwie|wzgar=dli=wie|11 +Wójtowa|Wój=to=wa|11 +wójtowej|wój=to=wej|11 +zabawa|za=ba=wa|11 +zabrać|za=brać|11 +zaczną|za=czną|11 +zagadnęła|za=gad=nę=ła|11 +zagrały|za=gra=ły|11 +zapatrzony|za=pa=trzo=ny|11 +zapowiedzie|za=po=wie=dzie|11 +zapraszała|za=pra=sza=ła|11 +zasnął|za=snął|11 +zaszło|za=szło|11 +zatrzęsła|za=trzę=sła|11 +zatrzęsły|za=trzę=sły|11 +zebrała|ze=bra=ła|11 +zebrało|ze=bra=ło|11 +zerwała|ze=rwa=ła|11 +zgodzie|zgo=dzie|11 +zjedzą|zje=dzą|11 +zmroku|zmro=ku|11 +znajomków|zna=jom=ków|11 +znaleźć|zna=leźć|11 +zwrócił|zwró=cił|11 +ściana|ścia=na|11 +świętego|świę=te=go|11 +żałował|ża=ło=wał|11 +żałość|ża=łość|11 +Balcerek|Bal=ce=rek|10 +baldach|bal=dach|10 +biedzie|bie=dzie|10 +bokach|bo=kach|10 +bokiem|bo=kiem|10 +bratem|bra=tem|10 +bruzdach|bruz=dach|10 +brzęczały|brzę=cza=ły|10 +brzękiem|brzę=kiem|10 +chciały|chcia=ły|10 +chrzciny|chrzci=ny|10 +chórem|chó=rem|10 +Chłopaki|Chło=pa=ki|10 +chłopcy|chłop=cy|10 +chłopem|chło=pem|10 +Cichocie|Ci=cho=cie|10 +cichuśkim|ci=chuś=kim|10 +ciekły|cie=kły|10 +ciemnicy|ciem=ni=cy|10 +cienia|cie=nia|10 +ciepłe|cie=płe|10 +czarnych|czar=nych|10 +czarną|czar=ną|10 +Częstochowskiej|Czę=sto=chow=skiej|10 +dajcie|daj=cie|10 +daleki|da=le=ki|10 +deszcze|desz=cze|10 +dobrodziejem|do=bro=dzie=jem|10 +dobrych|do=brych|10 +dróżkę|dróż=kę|10 +dwojaków|dwo=ja=ków|10 +dzieuchami|dzieu=cha=mi|10 +długiej|dłu=giej|10 +Filipka|Fi=lip=ka|10 +Franek|Fra=nek|10 +gardło|gar=dło|10 +gospodarkę|go=spo=dar=kę|10 +gospodarski|go=spo=dar=ski|10 +gospodarstwa|go=spo=dar=stwa|10 +gotowego|go=to=we=go|10 +gromada|gro=ma=da|10 +gromady|gro=ma=dy|10 +grzechów|grze=chów|10 +Gulbasiak|Gul=ba=siak|10 +gąszcz|gąszcz|10 +gęstwę|gę=stwę|10 +głosować|gło=so=wać|10 +Głupia|Głu=pia|10 +huknął|huk=nął|10 +idźcie|idź=cie|10 +inszych|in=szych|10 +izbach|izbach|10 +jarmarek|jar=ma=rek|10 +jedzenie|je=dze=nie|10 +Jezusem|Je=zu=sem|10 +jąkała|ją=ka=ła|10 +jęczał|ję=czał|10 +Jędrek|Ję=drek|10 +kartofle|kar=to=fle|10 +karwas|kar=was|10 +kobietami|ko=bie=ta=mi|10 +kobietą|ko=bie=tą|10 +kokosz|ko=kosz|10 +kolebały|ko=le=ba=ły|10 +koszula|ko=szu=la|10 +koszulę|ko=szu=lę|10 +kowalowie|ko=wa=lo=wie|10 +Kozłową|Ko=zło=wą|10 +kredyt|kre=dyt|10 +krokiem|kro=kiem|10 +krzyczały|krzy=cza=ły|10 +krzycząc|krzy=cząc|10 +krzyże|krzy=że|10 +księża|księ=ża|10 +księżą|księ=żą|10 +któregoś|któ=re=goś|10 +kurzyło|ku=rzy=ło|10 +kądzielą|ką=dzie=lą|10 +leżało|le=ża=ło|10 +ludzkich|ludz=kich|10 +majaczyły|ma=ja=czy=ły|10 +mdlały|mdla=ły|10 +miałem|mia=łem|10 +miedzy|mie=dzy|10 +milczał|mil=czał|10 +morgach|mor=gach|10 +moście|mo=ście|10 +muzykę|mu=zy=kę|10 +Mówili|Mó=wi=li|10 +mądrala|mą=dra=la|10 +Młynarz|Mły=narz|10 +nadeszła|na=de=szła|10 +nadszedł|nad=szedł|10 +najlepszy|naj=lep=szy|10 +Nastką|Na=st=ką|10 +naszczekiwały|na=szcze=ki=wa=ły|10 +niemałym|nie=ma=łym|10 +nieobjęte|nie=ob=ję=te|10 +Nieprawda|Nie=praw=da|10 +nieszpory|nie=szpo=ry|10 +nowych|no=wych|10 +obcierał|ob=cie=rał|10 +Obejrzał|Obej=rzał|10 +obraza|ob=ra=za|10 +obrazami|ob=ra=za=mi|10 +odebrać|ode=brać|10 +odezwał|ode=zwał|10 +odezwała|ode=zwa=ła|10 +odpocząć|od=po=cząć|10 +odzywał|od=zy=wał|10 +ogarniając|ogar=nia=jąc|10 +ogonem|ogo=nem|10 +opadały|opa=da=ły|10 +organów|or=ga=nów|10 +osobno|osob=no|10 +pachnące|pach=ną=ce|10 +pachnący|pach=ną=cy|10 +paciorki|pa=cior=ki|10 +piasek|pia=sek|10 +pierwszych|pierw=szych|10 +Pietrkowi|Pietr=ko=wi|10 +Pietruś|Pie=truś|10 +Pietrze|Pie=trze|10 +piętnaście|pięt=na=ście|10 +pięści|pię=ści|10 +poczęli|po=czę=li|10 +poczęła|po=czę=ła|10 +podnieść|pod=nieść|10 +podobne|po=dob=ne|10 +podłodze|pod=ło=dze|10 +pogasły|po=ga=sły|10 +poglądając|po=glą=da=jąc|10 +pojedynkę|po=je=dyn=kę|10 +pokazywał|po=ka=zy=wał|10 +pokaże|po=ka=że|10 +pomiarkować|po=miar=ko=wać|10 +pomocą|po=mo=cą|10 +pomyślała|po=my=śla=ła|10 +ponuro|po=nu=ro|10 +porozchodzili|po=roz=cho=dzi=li|10 +Poszedł|Po=szedł|10 +potrzebuje|po=trze=bu=je|10 +Powiadali|Po=wia=da=li|10 +powiedz|po=wiedz|10 +powinien|po=wi=nien|10 +powstrzymać|po=wstrzy=mać|10 +procesji|pro=ce=sji|10 +przejmowała|przej=mo=wa=ła|10 +przekpinki|prze=kpin=ki|10 +przenikliwie|prze=ni=kli=wie|10 +przerażenia|prze=ra=że=nia|10 +przerwy|prze=rwy|10 +przesiaduje|prze=sia=du=je|10 +przychodziła|przy=cho=dzi=ła|10 +przyjdź|przyjdź|10 +przypołudnie|przy=po=łu=dnie|10 +przywieźli|przy=wieź=li|10 +psiachmać|psiach=mać|10 +płacić|pła=cić|10 +płakaniem|pła=ka=niem|10 +płomieniami|pło=mie=nia=mi|10 +radosne|ra=do=sne|10 +radosny|ra=do=sny|10 +radoście|ra=do=ście|10 +rozpowiadając|roz=po=wia=da=jąc|10 +rozpowiadała|roz=po=wia=da=ła|10 +rozważać|roz=wa=żać|10 +rośnie|ro=śnie|10 +rykiem|ry=kiem|10 +rymnął|rym=nął|10 +rzucili|rzu=ci=li|10 +różańca|ró=żań=ca|10 +schował|scho=wał|10 +siedziało|sie=dzia=ło|10 +Sikora|Si=ko=ra|10 +skoczyła|sko=czy=ła|10 +skończyło|skoń=czy=ło|10 +skądciś|skąd=ciś|10 +sobotę|so=bo=tę|10 +spadały|spa=da=ły|10 +spojrzeć|spoj=rzeć|10 +spostrzegł|spo=strzegł|10 +sprzeciw|sprze=ciw|10 +sprzedać|sprze=dać|10 +sprzęt|sprzęt|10 +spływały|spły=wa=ły|10 +stanęli|sta=nę=li|10 +starej|sta=rej|10 +starsi|star=si|10 +stodole|sto=do=le|10 +szarych|sza=rych|10 +szczerze|szcze=rze|10 +szeroką|sze=ro=ką|10 +szkoły|szko=ły|10 +szpitala|szpi=ta=la|10 +szumem|szu=mem|10 +słodką|słod=ką|10 +służył|słu=żył|10 +tańcowali|tań=co=wa=li|10 +tańcować|tań=co=wać|10 +Tereskę|Te=re=skę|10 +trumnę|trum=nę|10 +trzaskały|trza=ska=ły|10 +trzymają|trzy=ma=ją|10 +trzymając|trzy=ma=jąc|10 +trzymali|trzy=ma=li|10 +trzęsło|trzę=sło|10 +tuliła|tu=li=ła|10 +twarde|twar=de|10 +twardy|twar=dy|10 +uniesła|unie=sła|10 +urzędzie|urzę=dzie|10 +usiadł|usiadł|10 +uważać|uwa=żać|10 +uwierzyć|uwie=rzyć|10 +Wachnik|Wach=nik|10 +walili|wa=li=li|10 +waliły|wa=li=ły|10 +weszli|we=szli|10 +weźcie|weź=cie|10 +widniał|wid=niał|10 +widocznie|wi=docz=nie|10 +wisiał|wi=siał|10 +wnetki|wnet=ki|10 +wolność|wol=ność|10 +wozach|wo=zach|10 +wołania|wo=ła=nia|10 +wrzątku|wrząt=ku|10 +wschodzie|wscho=dzie|10 +wsparł|wsparł|10 +wstrzymał|wstrzy=mał|10 +wszelki|wszel=ki|10 +wybrany|wy=bra=ny|10 +wydzierał|wy=dzie=rał|10 +wydzierały|wy=dzie=ra=ły|10 +wyglądała|wy=glą=da=ła|10 +wyjrzała|wyj=rza=ła|10 +wyprawia|wy=pra=wia|10 +wyrabia|wy=ra=bia|10 +wyrwało|wy=rwa=ło|10 +wysoki|wy=so=ki|10 +wysunął|wy=su=nął|10 +wywarty|wy=war=ty|10 +zagadał|za=ga=dał|10 +zagonów|za=go=nów|10 +zagrała|za=gra=ła|10 +zalały|za=la=ły|10 +zapalił|za=pa=lił|10 +zapowietrzone|za=po=wie=trzo=ne|10 +zapłaci|za=pła=ci|10 +zaraza|za=ra=za|10 +zawracał|za=wra=cał|10 +zbożach|zbo=żach|10 +zbraknie|zbrak=nie|10 +zebrać|ze=brać|10 +zeźlony|ze=źlo=ny|10 +zgodnie|zgod=nie|10 +zielenią|zie=le=nią|10 +ziemiami|zie=mia=mi|10 +zważając|zwa=ża=jąc|10 +zwijała|zwi=ja=ła|10 +ścianie|ścia=nie|10 +ściągali|ścią=ga=li|10 +ślepiów|śle=piów|10 +śliczności|ślicz=no=ści|10 +śmiertelny|śmier=tel=ny|10 +śpiewali|śpie=wa=li|10 +śpiewań|śpie=wań|10 +świecił|świe=cił|10 +żałosnym|ża=ło=snym|10 +żałośliwie|ża=ło=śli=wie|10 +żegnał|że=gnał|10 +Bartka|Bart=ka|9 +białym|bia=łym|9 +bieliły|bie=li=ły|9 +bolały|bo=la=ły|9 +boleści|bo=le=ści|9 +Borynowa|Bo=ry=no=wa|9 +Borynową|Bo=ry=no=wą|9 +boskie|bo=skie|9 +bracie|bra=cie|9 +buchała|bu=cha=ła|9 +budzić|bu=dzić|9 +buzował|bu=zo=wał|9 +byłoby|by=ło=by|9 +Chciała|Chcia=ła|9 +chciałem|chcia=łem|9 +chciwie|chci=wie|9 +chmurzyska|chmu=rzy=ska|9 +Chodził|Cho=dził|9 +chusty|chu=s=ty|9 +chętliwie|chę=tli=wie|9 +chętnie|chęt=nie|9 +Chłopak|Chło=pak|9 +Cichość|Ci=chość|9 +cichych|ci=chych|9 +ciemię|cie=mię|9 +cieniami|cie=nia=mi|9 +cisnęli|ci=snę=li|9 +ciągnie|cią=gnie|9 +ciągną|cią=gną|9 +ciągotki|cią=got=ki|9 +ciężką|cięż=ką|9 +cudzego|cu=dze=go|9 +Czasem|Cza=sem|9 +Czemuż|Cze=muż|9 +czerwono|czer=wo=no|9 +czystym|czy=stym|9 +czytać|czy=tać|9 +człowiekowa|czło=wie=ko=wa|9 +dalekości|da=le=ko=ści|9 +dawnemu|daw=ne=mu|9 +dobrotliwie|do=bro=tli=wie|9 +dobrość|do=brość|9 +dojrzeć|doj=rzeć|9 +dostać|do=stać|9 +dosłyszał|do=sły=szał|9 +drewniane|drew=nia=ne|9 +drobny|drob=ny|9 +drużbowie|druż=bo=wie|9 +drzewach|drze=wach|9 +drzewinami|drze=wi=na=mi|9 +duszno|dusz=no|9 +dychać|dy=chać|9 +dziadów|dzia=dów|9 +dziękować|dzię=ko=wać|9 +długich|dłu=gich|9 +dźwigać|dźwi=gać|9 +figlów|fi=glów|9 +figury|fi=gu=ry|9 +figurą|fi=gu=rą|9 +garnki|garn=ki|9 +godzić|go=dzić|9 +gorący|go=rą=cy|9 +gospodarskie|go=spo=dar=skie|9 +gospodarsku|go=spo=dar=sku|9 +Gospodyni|Go=spo=dy=ni|9 +gotowi|go=to=wi|9 +grania|gra=nia|9 +gronta|gron=ta|9 +gruchnął|gruch=nął|9 +grzmoty|grzmo=ty|9 +głownie|głow=nie|9 +głupawy|głu=pa=wy|9 +Głupie|Głu=pie|9 +głębokie|głę=bo=kie|9 +herbatę|her=ba=tę|9 +jakbyś|jak=byś|9 +jastrząb|ja=strząb|9 +Jastrząb|Ja=strząb|9 +jednych|jed=nych|9 +jeździł|jeź=dził|9 +kapusta|ka=pu=sta|9 +klepisko|kle=pi=sko|9 +kobiece|ko=bie=ce|9 +koguty|ko=gu=ty|9 +kompanii|kom=pa=nii|9 +koniczynę|ko=ni=czy=nę|9 +kopyta|ko=py=ta|9 +kruchcie|kruch=cie|9 +krzyku|krzy=ku|9 +krzyków|krzy=ków|9 +krzywdą|krzyw=dą|9 +kudłów|ku=dłów|9 +kładła|kła=dła|9 +kłosami|kło=sa=mi|9 +Kłębiak|Kłę=biak|9 +Kłębową|Kłę=bo=wą|9 +lamenty|la=men=ty|9 +Ledwie|Le=d=wie|9 +Lipczaki|Lip=cza=ki|9 +lubości|lu=bo=ści|9 +Mateuszowi|Ma=te=uszo=wi|9 +miedzę|mie=dzę|9 +miejsc|miejsc|9 +miesiąc|mie=siąc|9 +minuty|mi=nu=ty|9 +miętko|mięt=ko|9 +miłowania|mi=ło=wa=nia|9 +modrym|mo=drym|9 +mrozie|mro=zie|9 +musiały|mu=sia=ły|9 +myślach|my=ślach|9 +myślałam|my=śla=łam|9 +mówcie|mów=cie|9 +młodzi|mło=dzi|9 +Naczelnik|Na=czel=nik|9 +naczelnika|na=czel=ni=ka|9 +naczynie|na=czy=nie|9 +nadział|na=dział|9 +nagich|na=gich|9 +nastał|na=stał|9 +Nastusią|Na=stu=sią|9 +natknął|na=tknął|9 +niechcący|nie=chcą=cy|9 +niejednego|nie=jed=ne=go|9 +niejedną|nie=jed=ną|9 +nieszczęście|nie=szczę=ście|9 +nieustępliwa|nie=ustę=pli=wa|9 +niewiele|nie=wie=le|9 +nosiła|no=si=ła|9 +nowiną|no=wi=ną|9 +obaczy|oba=czy|9 +obaczycie|oba=czy=cie|9 +obchodził|ob=cho=dził|9 +obejmując|obej=mu=jąc|9 +obejście|obej=ście|9 +obiecał|obie=cał|9 +odpowiedział|od=po=wie=dział|9 +ogarnął|ogar=nął|9 +ogrodach|ogro=dach|9 +ojcową|oj=co=wą|9 +okapem|oka=pem|9 +opowiedział|opo=wie=dział|9 +ostatka|ostat=ka|9 +ostatnich|ostat=nich|9 +otworzyć|otwo=rzyć|9 +pacierzu|pa=cie=rzu|9 +padało|pa=da=ło|9 +paskudne|pa=skud=ne|9 +Pańskie|Pań=skie|9 +piechty|piech=ty|9 +piekło|pie=kło|9 +pierwej|pier=wej|9 +piesek|pie=sek|9 +pieska|pie=ska|9 +pilnuj|pil=nuj|9 +pochwalił|po=chwa=lił|9 +pochylonych|po=chy=lo=nych|9 +pociechy|po=cie=chy|9 +poczciwy|po=czci=wy|9 +Poczekaj|Po=cze=kaj|9 +początku|po=cząt=ku|9 +podatki|po=dat=ki|9 +podała|po=da=ła|9 +podczas|pod=czas|9 +Podniesienia|Pod=nie=sie=nia|9 +podnosiło|pod=no=si=ło|9 +podobien|po=do=bien|9 +podziwu|po=dzi=wu|9 +pogadał|po=ga=dał|9 +poglądała|po=glą=da=ła|9 +pojadę|po=ja=dę|9 +pokazała|po=ka=za=ła|9 +pokoju|po=ko=ju|9 +pokryty|po=kry=ty|9 +poletę|po=le=tę|9 +pomstował|po=msto=wał|9 +popatrzeć|po=pa=trzeć|9 +popatrzył|po=pa=trzył|9 +poprzek|po=przek|9 +popłakując|po=pła=ku=jąc|9 +poradził|po=ra=dził|9 +poruszyć|po=ru=szyć|9 +postronku|po=stron=ku|9 +poszukać|po=szu=kać|9 +posłyszawszy|po=sły=szaw=szy|9 +potrzebny|po=trzeb=ny|9 +powiadam|po=wia=dam|9 +powtórzył|po=wtó=rzył|9 +poznał|po=znał|9 +prażył|pra=żył|9 +proboszcza|pro=bosz=cza|9 +procesją|pro=ce=sją|9 +prosiak|pro=siak|9 +przechodziły|prze=cho=dzi=ły|9 +przecierał|prze=cie=rał|9 +przecknęła|prze=ck=nę=ła|9 +przede|przede|9 +przegarniał|prze=gar=niał|9 +przejęte|prze=ję=te|9 +przeleciał|prze=le=ciał|9 +przemian|prze=mian|9 +przepijał|prze=pi=jał|9 +przepił|prze=pił|9 +przestając|prze=sta=jąc|9 +przestrzał|prze=strzał|9 +przeszły|prze=szły|9 +przełaj|prze=łaj|9 +przełazu|prze=ła=zu|9 +przygarnął|przy=gar=nął|9 +przyjacielstwo|przy=ja=ciel=stwo|9 +przykry|przy=kry|9 +przypomniał|przy=po=mniał|9 +przystawali|przy=sta=wa=li|9 +Próżno|Próż=no|9 +półgłosem|pół=gło=sem|9 +półkwaterki|pół=kwa=ter=ki|9 +płakania|pła=ka=nia|9 +płótna|płót=na|9 +radzić|ra=dzić|9 +rejwach|rej=wach|9 +rodzona|ro=dzo=na|9 +rosami|ro=sa=mi|9 +rozgłośniej|roz=gło=śniej|9 +rozległy|roz=le=gły|9 +rozmyślała|roz=my=śla=ła|9 +rozstajach|roz=sta=jach|9 +rychtować|rych=to=wać|9 +Rychło|Ry=chło|9 +ryczała|ry=cza=ła|9 +rzeźwe|rzeź=we|9 +rzęsisty|rzę=si=sty|9 +sercach|ser=cach|9 +sielna|siel=na|9 +siostry|sio=stry|9 +siostrą|sio=strą|9 +sięgnąć|się=gnąć|9 +skargi|skar=gi|9 +skargą|skar=gą|9 +skargę|skar=gę|9 +skwapliwie|skwa=pli=wie|9 +smutne|smut=ne|9 +smętarza|smę=ta=rza|9 +spadnie|spad=nie|9 +sparło|spar=ło|9 +spaśny|spa=śny|9 +spotkała|spo=tka=ła|9 +sprawie|spra=wie|9 +stamtąd|stam=tąd|9 +starych|sta=rych|9 +starzy|sta=rzy|9 +stawać|sta=wać|9 +stojący|sto=ją=cy|9 +Strach|Strach|9 +stracił|stra=cił|9 +straszna|strasz=na|9 +strasznego|strasz=ne=go|9 +straszny|strasz=ny|9 +stworzył|stwo=rzył|9 +stójka|stój=ka|9 +syknęła|syk=nę=ła|9 +szczekać|szcze=kać|9 +szczęściem|szczę=ściem|9 +Szczęściem|Szczę=ściem|9 +szybki|szyb=ki|9 +słowami|sło=wa=mi|9 +słońcem|słoń=cem|9 +słucha|słu=cha|9 +służbie|służ=bie|9 +słówkiem|słów=kiem|9 +tabaki|ta=ba=ki|9 +troskliwie|tro=skli=wie|9 +trzecią|trze=cią|9 +trzeszczały|trzesz=cza=ły|9 +turkotały|tur=ko=ta=ły|9 +twardym|twar=dym|9 +uciechą|ucie=chą|9 +ukradł|ukradł|9 +uniósł|uniósł|9 +ustami|usta=mi|9 +uśmiechał|uśmie=chał|9 +wchodził|wcho=dził|9 +wełniaków|weł=nia=ków|9 +wiatry|wia=try|9 +widziałem|wi=dzia=łem|9 +widzieć|wi=dzieć|9 +wieczory|wie=czo=ry|9 +wieków|wie=ków|9 +Wielki|Wiel=ki|9 +winowata|wi=no=wa=ta|9 +wlazło|wla=zło|9 +wolnelektury|wol=ne=lek=tu=ry|9 +wracał|wra=cał|9 +wrzeciono|wrze=cio=no|9 +wrzątkiem|wrząt=kiem|9 +wspierając|wspie=ra=jąc|9 +wstała|wsta=ła|9 +wstrzymać|wstrzy=mać|9 +wsunął|wsu=nął|9 +wszyćkie|wszyć=kie|9 +wtrącił|wtrą=cił|9 +wtrąciła|wtrą=ci=ła|9 +wybierać|wy=bie=rać|9 +wybierał|wy=bie=rał|9 +wyciągając|wy=cią=ga=jąc|9 +wyciągnąć|wy=cią=gnąć|9 +wyglądając|wy=glą=da=jąc|9 +wyjrzeć|wyj=rzeć|9 +wypadnie|wy=pad=nie|9 +wypominać|wy=po=mi=nać|9 +wypytywać|wy=py=ty=wać|9 +wyrobek|wy=ro=bek|9 +wyrzekania|wy=rze=ka=nia|9 +wzdychy|wzdy=chy|9 +wójtową|wój=to=wą|9 +Zaczęła|Za=czę=ła|9 +zamysły|za=my=sły|9 +zaniesła|za=nie=sła|9 +zapadła|za=pa=dła|9 +zapalczywie|za=pal=czy=wie|9 +zapłacę|za=pła=cę|9 +zapłakała|za=pła=ka=ła|9 +zaradzić|za=ra=dzić|9 +zazierać|za=zie=rać|9 +zbiera|zbie=ra|9 +zdumieniem|zdu=mie=niem|9 +zdumieniu|zdu=mie=niu|9 +zdumiony|zdu=mio=ny|9 +zeszło|ze=szło|9 +zeźlił|ze=źlił|9 +zieleni|zie=le=ni|9 +ziemniakami|ziem=nia=ka=mi|9 +zmówiny|zmó=wi=ny|9 +został|zo=stał|9 +zuchwale|zu=chwa=le|9 +zwarli|zwar=li|9 +zwiesnowy|zwie=sno=wy|9 +zwykle|zwy=kle|9 +złośliwie|zło=śli=wie|9 +łajdus|łaj=dus|9 +ławach|ła=wach|9 +łaziły|ła=zi=ły|9 +śmiało|śmia=ło|9 +śmiech|śmiech|9 +śmignął|śmi=gnął|9 +śpieszył|śpie=szył|9 +śpiewając|śpie=wa=jąc|9 +światłości|świa=tło=ści|9 +święcie|świę=cie|9 +żegnając|że=gna=jąc|9 +żółtych|żół=tych|9 +Alleluja|Al=le=lu=ja|8 +Antkową|Ant=ko=wą|8 +bacznie|bacz=nie|8 +batami|ba=ta=mi|8 +biczem|bi=czem|8 +biedoto|bie=do=to|8 +biegła|bie=gła|8 +broniąc|bro=niąc|8 +brzaskach|brza=skach|8 +brzucho|brzu=cho|8 +bydlątka|by=dląt=ka|8 +błocko|błoc=ko|8 +całunki|ca=łun=ki|8 +chwiał|chwiał|8 +chyciła|chy=ci=ła|8 +chłodzie|chło=dzie|8 +ciepłem|cie=płem|8 +ciepłych|cie=płych|8 +ciągał|cią=gał|8 +ciężkich|cięż=kich|8 +czajki|czaj=ki|8 +czapki|czap=ki|8 +Czekaj|Cze=kaj|8 +czerwonym|czer=wo=nym|8 +czerwonymi|czer=wo=ny=mi|8 +czwartek|czwar=tek|8 +czyniła|czy=ni=ła|8 +dachem|da=chem|8 +dawnej|daw=nej|8 +dawniej|daw=niej|8 +deszczem|desz=czem|8 +Dobranoc|Do=bra=noc|8 +Dołach|Do=łach|8 +druhny|druh=ny|8 +dysząc|dy=sząc|8 +dzieciak|dzie=ciak|8 +dzieciątka|dzie=ciąt=ka|8 +dziedzice|dzie=dzi=ce|8 +dziedzicowi|dzie=dzi=co=wi|8 +dziedziczka|dzie=dzicz=ka|8 +dziedziców|dzie=dzi=ców|8 +dziesięciu|dzie=się=ciu|8 +dziewkę|dziew=kę|8 +dzikim|dzi=kim|8 +dziwny|dziw=ny|8 +dziwował|dzi=wo=wał|8 +dzwonił|dzwo=nił|8 +Dębicy|Dę=bi=cy|8 +dłonią|dło=nią|8 +garnków|garn=ków|8 +godzinami|go=dzi=na=mi|8 +gorączkowo|go=rącz=ko=wo|8 +gospodarce|go=spo=dar=ce|8 +gołymi|go=ły=mi|8 +goście|go=ście|8 +granatowe|gra=na=to=we|8 +gryzły|gry=zły|8 +Grzelą|Grze=lą|8 +gwarno|gwar=no|8 +gwiazd|gwiazd|8 +gwiazda|gwiaz=da|8 +głuchym|głu=chym|8 +huczała|hu=cza=ła|8 +huczało|hu=cza=ło|8 +Jeszczech|Jesz=czech|8 +Jezusowi|Je=zu=so=wi|8 +jęczała|ję=cza=ła|8 +jęczmiona|jęcz=mio=na|8 +kamieni|ka=mie=ni|8 +kamionki|ka=mion=ki|8 +kieliszki|kie=lisz=ki|8 +kluski|klu=ski|8 +kobiecie|ko=bie=cie|8 +Kobusowa|Ko=bu=so=wa|8 +Kozłów|Ko=złów|8 +kołtuny|koł=tu=ny|8 +kościach|ko=ściach|8 +krajach|kra=jach|8 +krwawym|krwa=wym|8 +krzyżów|krzy=żów|8 +książką|książ=ką|8 +Księżyc|Księ=życ|8 +Któren|Któ=ren|8 +kulasów|ku=la=sów|8 +kupami|ku=pa=mi|8 +kupili|ku=pi=li|8 +kwikiem|kwi=kiem|8 +kłopot|kło=pot|8 +kłócić|kłó=cić|8 +latała|la=ta=ła|8 +ludzkiej|ludz=kiej|8 +maciory|ma=cio=ry|8 +miarkuję|miar=ku=ję|8 +mieniło|mie=ni=ło|8 +mierziło|mier=zi=ło|8 +migotały|mi=go=ta=ły|8 +Milczeli|Mil=cze=li|8 +miodem|mio=dem|8 +miłosierdzia|mi=ło=sier=dzia|8 +miłosierdzie|mi=ło=sier=dzie|8 +mlekiem|mle=kiem|8 +mniejsze|mniej=sze|8 +Moiście|Mo=iście|8 +myśleli|my=śle=li|8 +mówiłem|mó=wi=łem|8 +mówiły|mó=wi=ły|8 +młynarzowa|mły=na=rzo=wa|8 +młynicy|mły=ni=cy|8 +nabierał|na=bie=rał|8 +nabrane|na=bra=ne|8 +naciskiem|na=ci=skiem|8 +najpierw|naj=pierw|8 +największy|naj=więk=szy|8 +namawiał|na=ma=wiał|8 +narządziła|na=rzą=dzi=ła|8 +Nastkę|Na=st=kę|8 +naszej|na=szej|8 +naszemu|na=sze=mu|8 +nasłuchiwała|na=słu=chi=wa=ła|8 +Nazajutrz|Na=za=jutrz|8 +niebem|nie=bem|8 +niecierpliwiej|nie=cier=pli=wiej|8 +niedawno|nie=daw=no|8 +niedziel|nie=dziel|8 +niepokój|nie=po=kój|8 +nieznacznie|nie=znacz=nie|8 +nijakiego|ni=ja=kie=go|8 +nijako|ni=ja=ko|8 +nikogój|ni=ko=gój|8 +obaczę|oba=czę|8 +obcych|ob=cych|8 +objęła|ob=ję=ła|8 +obleczenie|ob=le=cze=nie|8 +obronie|obro=nie|8 +obronę|obro=nę|8 +obzierał|obzie=rał|8 +ochoty|ocho=ty|8 +odburknął|od=burk=nął|8 +odparł|od=parł|8 +opadał|opa=dał|8 +opiekę|opie=kę|8 +Organista|Or=ga=ni=sta|8 +ostawił|osta=wił|8 +Ostańcie|Ostań=cie|8 +pachniało|pach=nia=ło|8 +pachnącym|pach=ną=cym|8 +palące|pa=lą=ce|8 +papiery|pa=pie=ry|8 +parafii|pa=ra=fii|8 +parobku|pa=rob=ku|8 +patrzyły|pa=trzy=ły|8 +pełnymi|peł=ny=mi|8 +piasku|pia=sku|8 +pieniądz|pie=niądz|8 +pierwsi|pierw=si|8 +piorunami|pio=ru=na=mi|8 +Pisarz|Pi=sarz|8 +pisarza|pi=sa=rza|8 +pięćdziesiąt|pięć=dzie=siąt|8 +placek|pla=cek|8 +placka|plac=ka|8 +plucha|plu=cha|8 +pobiegł|po=biegł|8 +pochwalony|po=chwa=lo=ny|8 +pochylał|po=chy=lał|8 +pociemniało|po=ciem=nia=ło|8 +poczciwe|po=czci=we|8 +poczciwości|po=czci=wo=ści|8 +poczekać|po=cze=kać|8 +poczerniałe|po=czer=nia=łe|8 +podając|po=da=jąc|8 +podniosły|pod=nio=sły|8 +podoba|po=do=ba|8 +podsunął|pod=su=nął|8 +poglądali|po=glą=da=li|8 +pogrzeb|po=grzeb|8 +pokrzyki|po=krzy=ki|8 +pokrzykując|po=krzy=ku=jąc|8 +polecę|po=le=cę|8 +Polska|Pol=ska|8 +pomogę|po=mo=gę|8 +pomsty|po=msty|8 +ponosi|po=no=si|8 +poratunku|po=ra=tun=ku|8 +poredzali|po=re=dza=li|8 +porwały|po=rwa=ły|8 +Poszli|Po=szli|8 +Poszła|Po=szła|8 +posłuchać|po=słu=chać|8 +potężnie|po=tęż=nie|8 +powiadacie|po=wia=da=cie|8 +powiecie|po=wie=cie|8 +Powiem|Po=wiem|8 +powodu|po=wo=du|8 +powrocie|po=wro=cie|8 +powstrzymując|po=wstrzy=mu=jąc|8 +powtarzał|po=wta=rzał|8 +powtarzała|po=wta=rza=ła|8 +poznać|po=znać|8 +pozwolą|po=zwo=lą|8 +połednia|po=łed=nia|8 +położył|po=ło=żył|8 +połyskiwały|po=ły=ski=wa=ły|8 +Prawie|Pra=wie|8 +procesja|pro=ce=sja|8 +prosili|pro=si=li|8 +prostował|pro=sto=wał|8 +przechwalał|prze=chwa=lał|8 +przecknął|prze=ck=nął|8 +przedzierał|prze=dzie=rał|8 +przedzwaniali|prze=dzwa=nia=li|8 +przejmujący|przej=mu=ją=cy|8 +przejęła|prze=ję=ła|8 +przekąsem|prze=ką=sem|8 +przemóc|prze=móc|8 +przenieść|prze=nieść|8 +przepadł|prze=padł|8 +przepuszczając|prze=pusz=cza=jąc|8 +przerwała|prze=rwa=ła|8 +przeszli|prze=szli|8 +przewalał|prze=wa=lał|8 +przewiady|prze=wia=dy|8 +przełazie|prze=ła=zie|8 +przygotowań|przy=go=to=wań|8 +przyjacielsko|przy=ja=ciel=sko|8 +przymrozek|przy=mro=zek|8 +przypilnuję|przy=pil=nu=ję|8 +przypominki|przy=po=min=ki|8 +przystąpił|przy=stą=pił|8 +przysunął|przy=su=nął|8 +Przyszedł|Przy=szedł|8 +przysłaniały|przy=sła=nia=ły|8 +przywarte|przy=war=te|8 +przywierając|przy=wie=ra=jąc|8 +przyźbie|przy=źbie|8 +próbował|pró=bo=wał|8 +psiekrwie|psie=kr=wie|8 +ptaków|pta=ków|8 +pustym|pu=stym|8 +puszczając|pusz=cza=jąc|8 +puszył|pu=szył|8 +płaczliwie|płacz=li=wie|8 +płomieniem|pło=mie=niem|8 +Płoszków|Płosz=ków|8 +raptem|rap=tem|8 +robiły|ro=bi=ły|8 +rozebrać|ro=ze=brać|8 +rozmawiać|roz=ma=wiać|8 +roznosił|roz=no=sił|8 +rozpacznie|roz=pacz=nie|8 +rozpierała|roz=pie=ra=ła|8 +rozpytywać|roz=py=ty=wać|8 +rozumiała|ro=zu=mia=ła|8 +ruchał|ru=chał|8 +Rzekliście|Rze=kli=ście|8 +rządzi|rzą=dzi|8 +róbcie|rób=cie|8 +różaniec|ró=ża=niec|8 +samotne|sa=mot=ne|8 +samotnie|sa=mot=nie|8 +serdecznym|ser=decz=nym|8 +siarczyste|siar=czy=ste|8 +Siedział|Sie=dział|8 +Siedzieli|Sie=dzie=li|8 +sielne|siel=ne|8 +Sielny|Siel=ny|8 +sienie|sie=nie|8 +sięgał|się=gał|8 +skrzybot|skrzy=bot|8 +skrzypiały|skrzy=pia=ły|8 +smutna|smut=na|8 +spadły|spa=dły|8 +sposobną|spo=sob=ną|8 +spostrzegli|spo=strze=gli|8 +spuścić|spu=ścić|8 +srogiej|sro=giej|8 +srożył|sro=żył|8 +stawały|sta=wa=ły|8 +stojało|sto=ja=ło|8 +Strasznie|Strasz=nie|8 +straszno|strasz=no|8 +strażniki|straż=ni=ki|8 +stróżował|stró=żo=wał|8 +szczeka|szcze=ka|8 +szeroki|sze=ro=ki|8 +szkopek|szko=pek|8 +szkoła|szko=ła|8 +szkołach|szko=łach|8 +szukając|szu=ka=jąc|8 +szukały|szu=ka=ły|8 +szumiały|szu=mia=ły|8 +szybach|szy=bach|8 +słuchaj|słu=chaj|8 +słyszeli|sły=sze=li|8 +taczki|tacz=ki|8 +tknęło|tknę=ło|8 +tocząc|to=cząc|8 +topieli|to=pie=li|8 +topolami|to=po=la=mi|8 +torbeczki|tor=becz=ki|8 +trafił|tra=fił|8 +trupem|tru=pem|8 +Trzeba|Trze=ba|8 +trzeciego|trze=cie=go|8 +trzymam|trzy=mam|8 +tupoty|tu=po=ty|8 +turbacje|tur=ba=cje|8 +turkoty|tur=ko=ty|8 +tęskliwie|tę=skli=wie|8 +tłukąc|tłu=kąc|8 +tłukło|tłu=kło|8 +tłusto|tłu=sto|8 +urwała|urwa=ła|8 +uspokoić|uspo=ko=ić|8 +uspokoiło|uspo=ko=iło|8 +uszach|uszach|8 +utrzymać|utrzy=mać|8 +wagować|wa=go=wać|8 +westchnieniem|wes=tchnie=niem|8 +wetknęła|we=tknę=ła|8 +widziałam|wi=dzia=łam|8 +wierzył|wie=rzył|8 +wietrze|wie=trze|8 +Wilczych|Wil=czych|8 +wilgotny|wil=got=ny|8 +wionął|wio=nął|8 +wiosenny|wio=sen=ny|8 +wisiała|wi=sia=ła|8 +wiszące|wi=szą=ce|8 +wlekąc|wle=kąc|8 +wnątrzu|wną=trzu|8 +wodziła|wo=dzi=ła|8 +Wojtek|Woj=tek|8 +wracali|wra=ca=li|8 +wrzeciona|wrze=cio=na|8 +wsparła|wspar=ła|8 +wspomożenie|wspo=mo=że=nie|8 +wszelaki|wsze=la=ki|8 +wszyćkim|wszyć=kim|8 +wtykał|wty=kał|8 +wybrane|wy=bra=ne|8 +wychodząc|wy=cho=dząc|8 +wyciągały|wy=cią=ga=ły|8 +wyciągnęła|wy=cią=gnę=ła|8 +wyganiać|wy=ga=niać|8 +wyjściu|wyj=ściu|8 +wyniośle|wy=nio=śle|8 +wypominał|wy=po=mi=nał|8 +wypominki|wy=po=min=ki|8 +wyprostował|wy=pro=sto=wał|8 +wystąpił|wy=stą=pił|8 +wytrząchając|wy=trzą=cha=jąc|8 +wzdychała|wzdy=cha=ła|8 +wątpiach|wąt=piach|8 +wściekły|wście=kły|8 +zabaczyć|za=ba=czyć|8 +zabawę|za=ba=wę|8 +zabiega|za=bie=ga|8 +zacinał|za=ci=nał|8 +zaczynać|za=czy=nać|8 +Zaczęli|Za=czę=li|8 +zaglądając|za=glą=da=jąc|8 +zakotłowało|za=ko=tło=wa=ło|8 +zakończył|za=koń=czył|8 +zaledwie|za=le=d=wie|8 +zamieszanie|za=mie=sza=nie|8 +zapaski|za=pa=ski|8 +zapatrzyła|za=pa=trzy=ła|8 +zapierało|za=pie=ra=ło|8 +zapisał|za=pi=sał|8 +zapowietrzona|za=po=wie=trzo=na|8 +zatracenie|za=tra=ce=nie|8 +zawiewał|za=wie=wał|8 +zazierając|za=zie=ra=jąc|8 +zazwyczaj|za=zwy=czaj|8 +zgrozą|zgro=zą|8 +zjedli|zje=dli|8 +zmyślnie|zmyśl=nie|8 +znalazł|zna=lazł|8 +zresztą|zresz=tą|8 +zrobią|zro=bią|8 +zrywać|zry=wać|8 +zwierz|zwierz|8 +zwiesną|zwie=sną|8 +zziajany|zzia=ja=ny|8 +łagodnie|ła=god=nie|8 +Ścierwy|Ścier=wy|8 +ściągać|ścią=gać|8 +śmiały|śmia=ły|8 +środek|śro=dek|8 +świadków|świad=ków|8 +Święty|Świę=ty|8 +żadnego|żad=ne=go|8 +żałosny|ża=ło=sny|8 +żałoście|ża=ło=ście|8 +żołnierka|żoł=nier=ka|8 +żółciły|żół=ci=ły|8 +akurat|aku=rat|7 +alkierz|al=kierz|7 +Antkowa|Ant=ko=wa|7 +baczyła|ba=czy=ła|7 +bawiły|ba=wi=ły|7 +bezmyślnie|bez=myśl=nie|7 +białawe|bia=ła=we|7 +białej|bia=łej|7 +biedoty|bie=do=ty|7 +bielały|bie=la=ły|7 +bladym|bla=dym|7 +bogacz|bo=gacz|7 +bolesny|bo=le=sny|7 +borgować|bor=go=wać|7 +brakuje|bra=ku=je|7 +bruzdami|bruz=da=mi|7 +brzasków|brza=sków|7 +brzegi|brze=gi|7 +brzozy|brzo=zy|7 +budynki|bu=dyn=ki|7 +budził|bu=dził|7 +błogosławił|bło=go=sła=wił|7 +błotem|bło=tem|7 +błyskawice|bły=ska=wi=ce|7 +chceta|chce=ta|7 +chleby|chle=by|7 +chlewów|chle=wów|7 +chlustały|chlu=sta=ły|7 +chmurny|chmur=ny|7 +chodźcie|chodź=cie|7 +chojary|cho=ja=ry|7 +choroba|cho=ro=ba|7 +choroby|cho=ro=by|7 +Choćby|Choć=by|7 +chudziaku|chu=dzia=ku|7 +chwycić|chwy=cić|7 +ciasto|cia=sto|7 +cichej|ci=chej|7 +cichoj|ci=choj|7 +cichością|ci=cho=ścią|7 +ciekawe|cie=ka=we|7 +ciemne|ciem=ne|7 +ciepał|cie=pał|7 +ciesząc|cie=sząc|7 +ciągiem|cią=giem|7 +cofnęła|cof=nę=ła|7 +cudności|cud=no=ści|7 +czerwonej|czer=wo=nej|7 +czterdzieści|czter=dzie=ści|7 +Cztery|Czte=ry|7 +czubami|czu=ba=mi|7 +czyniło|czy=ni=ło|7 +czystej|czy=stej|7 +człowieczą|czło=wie=czą|7 +daleką|da=le=ką|7 +dalszych|dal=szych|7 +deszczów|desz=czów|7 +dniach|dniach|7 +dochodziły|do=cho=dzi=ły|7 +dostaniesz|do=sta=niesz|7 +dostała|do=sta=ła|7 +doszedł|do=szedł|7 +dowiedzieć|do=wie=dzieć|7 +dołkach|doł=kach|7 +dołożę|do=ło=żę|7 +drabiny|dra=bi=ny|7 +drzewin|drze=win|7 +dufności|duf=no=ści|7 +duszach|du=szach|7 +dziada|dzia=da|7 +dzieckiem|dziec=kiem|7 +dzikie|dzi=kie|7 +dzwonki|dzwon=ki|7 +długości|dłu=go=ści|7 +flachę|fla=chę|7 +flaszkę|flasz=kę|7 +gadali|ga=da=li|7 +gadała|ga=da=ła|7 +garściach|gar=ściach|7 +gmerały|gme=ra=ły|7 +gniazd|gniazd|7 +gniewy|gnie=wy|7 +gorzkie|gorz=kie|7 +gorączce|go=rącz=ce|7 +gospodarzem|go=spo=da=rzem|7 +gospodarzom|go=spo=da=rzom|7 +Gospodarzu|Go=spo=da=rzu|7 +gotowane|go=to=wa=ne|7 +Gołębianka|Go=łę=bian=ka|7 +gołębie|go=łę=bie|7 +grubym|gru=bym|7 +gwarzyć|gwa=rzyć|7 +głębiej|głę=biej|7 +głęboki|głę=bo=ki|7 +głębokiej|głę=bo=kiej|7 +innymi|in=ny=mi|7 +intencję|in=ten=cję|7 +jadłem|ja=dłem|7 +jakbym|jak=bym|7 +jakichś|ja=kichś|7 +Jambrożym|Jam=bro=żym|7 +jamory|ja=mo=ry|7 +jarzącymi|ja=rzą=cy=mi|7 +Jasiem|Ja=siem|7 +jasnym|ja=snym|7 +jechał|je=chał|7 +jenszy|jen=szy|7 +Jezusowe|Je=zu=so=we|7 +kajdany|kaj=da=ny|7 +katolickim|ka=to=lic=kim|7 +Każden|Każ=den|7 +kiecki|kiec=ki|7 +kiejście|kiej=ście|7 +klaczy|kla=czy|7 +kogutka|ko=gut=ka|7 +kopania|ko=pa=nia|7 +kopytami|ko=py=ta=mi|7 +korali|ko=ra=li|7 +kołowały|ko=ło=wa=ły|7 +kołysał|ko=ły=sał|7 +kołysały|ko=ły=sa=ły|7 +kończyna|koń=czy=na|7 +krajów|kra=jów|7 +krzami|krza=mi|7 +krzyknąć|krzyk=nąć|7 +krótką|krót=ką|7 +książkę|książ=kę|7 +księżego|księ=że=go|7 +Któregoś|Któ=re=goś|7 +kurzawą|ku=rza=wą|7 +kurzył|ku=rzył|7 +kwiatach|kwia=tach|7 +kądzieli|ką=dzie=li|7 +kłonicą|kło=ni=cą|7 +kłosów|kło=sów|7 +lecieli|le=cie=li|7 +lekciej|lek=ciej|7 +litanię|li=ta=nię|7 +lśniące|lśnią=ce|7 +marnego|mar=ne=go|7 +martwy|mar=twy|7 +matczyne|mat=czy=ne|7 +matula|ma=tu=la|7 +miarkujecie|miar=ku=je=cie|7 +miedzą|mie=dzą|7 +Miemce|Miem=ce|7 +minucie|mi=nu=cie|7 +minutę|mi=nu=tę|7 +miotał|mio=tał|7 +mocarz|mo=carz|7 +molestować|mo=le=sto=wać|7 +motyle|mo=ty=le|7 +możesz|mo=żesz|7 +mroczało|mro=cza=ło|7 +myślałem|my=śla=łem|7 +mógłby|mógł=by|7 +młodego|mło=de=go|7 +młodszy|młod=szy|7 +młynarzem|mły=na=rzem|7 +młynarzowego|mły=na=rzo=we=go|7 +młynarzów|mły=na=rzów|7 +nabrzmiałe|na=brzmia=łe|7 +nadzieją|na=dzie=ją|7 +Najpierw|Naj=pierw|7 +najpierwsi|naj=pierw=si|7 +nalazł|na=lazł|7 +namawiać|na=ma=wiać|7 +nasienia|na=sie=nia|7 +naszło|na=szło|7 +natomiast|na=to=miast|7 +niebieskie|nie=bie=skie|7 +nieboszczyka|nie=bosz=czy=ka|7 +niechaj|nie=chaj|7 +Niechby|Niech=by|7 +niektórzy|nie=któ=rzy|7 +niemałą|nie=ma=łą|7 +nienawiścią|nie=na=wi=ścią|7 +niesło|nie=sło|7 +Niewiela|Nie=wie=la|7 +nosili|no=si=li|7 +nowiny|no=wi=ny|7 +obcasami|ob=ca=sa=mi|7 +obertelek|obe=r=te=lek|7 +oberwać|obe=rwać|7 +obficie|ob=fi=cie|7 +obiecywał|obie=cy=wał|7 +obsiadły|ob=sia=dły|7 +ochotę|ocho=tę|7 +odchodzi|od=cho=dzi|7 +odchodził|od=cho=dził|7 +odchodząc|od=cho=dząc|7 +odetchnąć|ode=tchnąć|7 +odpowiedziała|od=po=wie=dzia=ła|7 +odzipnąć|od=zip=nąć|7 +oglądali|oglą=da=li|7 +ogniska|ogni=ska|7 +ogrodu|ogro=du|7 +ogrody|ogro=dy|7 +ogromnych|ogrom=nych|7 +opuszczenie|opusz=cze=nie|7 +opłatek|opła=tek|7 +opłotkami|opłot=ka=mi|7 +ostatnia|ostat=nia|7 +ostawał|osta=wał|7 +ostawiając|osta=wia=jąc|7 +ostawiła|osta=wi=ła|7 +ozorami|ozo=ra=mi|7 +ołtarze|oł=ta=rze|7 +pachnie|pach=nie|7 +padnie|pad=nie|7 +panowie|pa=no=wie|7 +paradą|pa=ra=dą|7 +Parobek|Pa=ro=bek|7 +patrzę|pa=trzę|7 +pazurów|pa=zu=rów|7 +Pańskiej|Pań=skiej|7 +pedzieć|pe=dzieć|7 +pełnej|peł=nej|7 +pełnym|peł=nym|7 +piesków|pie=sków|7 +pieśnią|pie=śnią|7 +Pijcie|Pij=cie|7 +pilnowała|pil=no=wa=ła|7 +pioruny|pio=ru=ny|7 +piosneczki|pio=snecz=ki|7 +piskiem|pi=skiem|7 +piąciu|pią=ciu|7 +plebanią|ple=ba=nią|7 +pluchę|plu=chę|7 +pobożny|po=boż=ny|7 +pochylały|po=chy=la=ły|7 +poczekaj|po=cze=kaj|7 +poczerwieniała|po=czer=wie=nia=ła|7 +pocznę|po=cznę|7 +podawać|po=da=wać|7 +podjął|pod=jął|7 +podnieśli|pod=nie=śli|7 +podobnie|po=dob=nie|7 +podobny|po=dob=ny|7 +podziw|po=dziw|7 +pogadując|po=ga=du=jąc|7 +pognała|po=gna=ła|7 +pojmować|poj=mo=wać|7 +pokazać|po=ka=zać|7 +pokojach|po=ko=jach|7 +Pokrótce|Po=krót=ce|7 +pokręciło|po=krę=ci=ło|7 +Poleciała|Po=le=cia=ła|7 +pomaga|po=ma=ga|7 +pomagała|po=ma=ga=ła|7 +pomstą|po=mstą|7 +pomyślenie|po=my=śle=nie|7 +pomógł|po=mógł|7 +pomówić|po=mó=wić|7 +poniesie|po=nie=sie|7 +poniesą|po=nie=są|7 +popijał|po=pi=jał|7 +poprawił|po=pra=wił|7 +popuszczę|po=pusz=czę|7 +poredzać|po=re=dzać|7 +poredzą|po=re=dzą|7 +poruszać|po=ru=szać|7 +porządków|po=rząd=ków|7 +poręby|po=rę=by|7 +porębę|po=rę=bę|7 +pospólnie|po=spól=nie|7 +postać|po=stać|7 +postronek|po=stro=nek|7 +posłuch|po=słuch|7 +posłyszeć|po=sły=szeć|7 +potaczał|po=ta=czał|7 +potrafi|po=tra=fi|7 +potrzeby|po=trze=by|7 +powiadać|po=wia=dać|7 +powiatu|po=wia=tu|7 +powiedam|po=wie=dam|7 +Powiedział|Po=wie=dział|7 +powraca|po=wra=ca|7 +powrócą|po=wró=cą|7 +powywierane|po=wy=wie=ra=ne|7 +pozostał|po=zo=stał|7 +pozwolić|po=zwo=lić|7 +połowa|po=ło=wa|7 +pośmiewisko|po=śmie=wi=sko|7 +pracował|pra=co=wał|7 +prosiaki|pro=sia=ki|7 +proszę|pro=szę|7 +prowadził|pro=wa=dził|7 +przechodziła|prze=cho=dzi=ła|7 +przedać|przedać|7 +przejmujące|przej=mu=ją=ce|7 +przejęły|prze=ję=ły|7 +przekpiwał|prze=kpi=wał|7 +przekładać|prze=kła=dać|7 +przenikał|prze=ni=kał|7 +przepijając|prze=pi=ja=jąc|7 +przestanku|prze=stan=ku|7 +Przeszedł|Prze=szedł|7 +przewiny|prze=wi=ny|7 +przewodził|prze=wo=dził|7 +przełaz|prze=łaz|7 +prześmiech|prze=śmiech|7 +przychodzą|przy=cho=dzą|7 +przychodź|przy=chodź|7 +przyczajone|przy=cza=jo=ne|7 +przydrożne|przy=droż=ne|7 +przyginały|przy=gi=na=ły|7 +przyglądał|przy=glą=dał|7 +Przyjdę|Przyj=dę|7 +przyjedzie|przy=je=dzie|7 +przynieść|przy=nieść|7 +przyobiecał|przy=obie=cał|7 +przypominając|przy=po=mi=na=jąc|7 +przysiedli|przy=sie=dli|7 +przysiąść|przy=siąść|7 +przyszedłem|przy=sze=dłem|7 +przywieść|przy=wieść|7 +przywiózł|przy=wiózł|7 +przyzba|przy=zba|7 +przyśpiewując|przy=śpie=wu=jąc|7 +próbowała|pró=bo=wa=ła|7 +Psiachmać|Psiach=mać|7 +ptactwa|ptac=twa|7 +ptasie|pta=sie|7 +płachta|płach=ta|7 +pławiły|pła=wi=ły|7 +płomienie|pło=mie=nie|7 +płomień|pło=mień|7 +radosną|ra=do=sną|7 +ratunek|ra=tu=nek|7 +rejenta|re=jen=ta|7 +robicie|ro=bi=cie|7 +robili|ro=bi=li|7 +robotny|ro=bot=ny|7 +rosach|ro=sach|7 +rozbierała|roz=bie=ra=ła|7 +rozbłysły|roz=bły=sły|7 +rozebrało|ro=ze=bra=ło|7 +rozgorzały|roz=go=rza=ły|7 +rozniesły|roz=nie=sły|7 +rozpalonymi|roz=pa=lo=ny=mi|7 +rozpiera|roz=pie=ra|7 +rozpoznał|roz=po=znał|7 +rozrosłe|roz=ro=słe|7 +rozumiał|ro=zu=miał|7 +rozumieć|ro=zu=mieć|7 +rozważając|roz=wa=ża=jąc|7 +rozłożył|roz=ło=żył|7 +ruszyły|ru=szy=ły|7 +Rzekłem|Rze=kłem|7 +Rzepek|Rze=pek|7 +rzęsiście|rzę=si=ście|7 +różańcem|ró=żań=cem|7 +samymi|sa=my=mi|7 +serdecznej|ser=decz=nej|7 +serdeczny|ser=decz=ny|7 +siadając|sia=da=jąc|7 +siekierą|sie=kie=rą|7 +Sikorzyna|Si=ko=rzy=na|7 +skraju|skra=ju|7 +skrzat|skrzat|7 +skrzyczał|skrzy=czał|7 +skrzypiał|skrzy=piał|7 +skrzypice|skrzy=pi=ce|7 +smętarzu|smę=ta=rzu|7 +snopki|snop=ki|7 +Sochowa|So=cho=wa|7 +sołtysa|soł=ty=sa|7 +spieki|spie=ki|7 +spodział|spo=dział|7 +spodziewając|spo=dzie=wa=jąc|7 +spojrzeniem|spoj=rze=niem|7 +spojrzy|spoj=rzy|7 +spostrzegając|spo=strze=ga=jąc|7 +spozierał|spo=zie=rał|7 +srebrne|srebr=ne|7 +srogich|sro=gich|7 +Stacha|Sta=cha|7 +stadko|stad=ko|7 +stawali|sta=wa=li|7 +stojali|sto=ja=li|7 +stojącą|sto=ją=cą|7 +straciła|stra=ci=ła|7 +strawy|stra=wy|7 +strażnikami|straż=ni=ka=mi|7 +strażnikom|straż=ni=kom|7 +struchlałe|stru=chla=łe|7 +strzegli|strze=gli|7 +strzelbę|strzel=bę|7 +stworzenia|stwo=rze=nia|7 +swobodnie|swo=bod=nie|7 +szarym|sza=rym|7 +szczekania|szcze=ka=nia|7 +szukała|szu=ka=ła|7 +słodkie|słod=kie|7 +Słyszeliście|Sły=sze=li=ście|7 +słyszysz|sły=szysz|7 +tabaką|ta=ba=ką|7 +tamtego|tam=te=go|7 +tatulu|ta=tu=lu|7 +toczył|to=czył|7 +trepami|tre=pa=mi|7 +trumny|trum=ny|7 +trwała|trwa=ła|7 +trzaskając|trza=ska=jąc|7 +trzasnęła|trza=snę=ła|7 +trzepały|trze=pa=ły|7 +tumany|tu=ma=ny|7 +tysiąc|ty=siąc|7 +ubrany|ubra=ny|7 +uciekały|ucie=ka=ły|7 +ucieszne|uciesz=ne|7 +uderzyły|ude=rzy=ły|7 +udręki|udrę=ki|7 +ujadały|uja=da=ły|7 +ujrzawszy|uj=rzaw=szy|7 +ująwszy|ująw=szy|7 +umiała|umia=ła|7 +umyślnie|umyśl=nie|7 +unosząc|uno=sząc|7 +upodoby|upodo=by|7 +urzędnik|urzęd=nik|7 +uspokoił|uspo=ko=ił|7 +usypany|usy=pa=ny|7 +uszami|usza=mi|7 +usłyszał|usły=szał|7 +uwierzy|uwie=rzy|7 +uwierzył|uwie=rzył|7 +uwięzi|uwię=zi|7 +uśmiechnął|uśmiech=nął|7 +warczały|war=cza=ły|7 +warkocze|war=ko=cze|7 +warowała|wa=ro=wa=ła|7 +waszych|wa=szych|7 +Weronki|We=ron=ki|7 +westchnienia|wes=tchnie=nia|7 +wiatrak|wia=trak|7 +wiecznie|wiecz=nie|7 +wiedła|wie=dła|7 +wiejskiej|wiej=skiej|7 +wierciło|wier=ci=ło|7 +wierzchu|wierz=chu|7 +wiośniane|wio=śnia=ne|7 +wiośnie|wio=śnie|7 +witając|wi=ta=jąc|7 +wiązać|wią=zać|7 +wojnie|woj=nie|7 +wolniuśko|wol=niuś=ko|7 +wozami|wo=za=mi|7 +wołały|wo=ła=ły|7 +wracając|wra=ca=jąc|7 +Wrzask|Wrzask|7 +wrzawą|wrza=wą|7 +wrzała|wrza=ła|7 +wrzeszczały|wrzesz=cza=ły|7 +wrócili|wró=ci=li|7 +wrótniami|wrót=nia=mi|7 +wschodem|wscho=dem|7 +wskazując|wska=zu=jąc|7 +współczuciem|współ=czu=ciem|7 +wstawać|wsta=wać|7 +wstąpił|wstą=pił|7 +wstążkę|wstąż=kę|7 +wybiera|wy=bie=ra|7 +wychylały|wy=chy=la=ły|7 +wydziera|wy=dzie=ra|7 +wygrażał|wy=gra=żał|7 +wyjrzał|wyj=rzał|7 +wykrzykiwać|wy=krzy=ki=wać|7 +wypomina|wy=po=mi=na|7 +wyrozumie|wy=ro=zu=mie|7 +wyrywać|wy=ry=wać|7 +wyrzekać|wy=rze=kać|7 +wystraszony|wy=stra=szo=ny|7 +wywalili|wy=wa=li=li|7 +wyzierając|wy=zie=ra=jąc|7 +wzburzony|wzbu=rzo=ny|7 +wzdychając|wzdy=cha=jąc|7 +wzdychów|wzdy=chów|7 +wznosił|wzno=sił|7 +wzruszenia|wzru=sze=nia|7 +wątroba|wą=tro=ba|7 +wściekłe|wście=kłe|7 +wściekłością|wście=kło=ścią|7 +zabiegał|za=bie=gał|7 +zabierać|za=bie=rać|7 +zachciało|za=chcia=ło|7 +zaczynał|za=czy=nał|7 +zaczynały|za=czy=na=ły|7 +zaglądać|za=glą=dać|7 +zaglądały|za=glą=da=ły|7 +zagonie|za=go=nie|7 +Zajrzała|Zaj=rza=ła|7 +zajęta|za=ję=ta|7 +zakołysał|za=ko=ły=sał|7 +zakrystii|za=kry=stii|7 +zakrzyczały|za=krzy=cza=ły|7 +zamedytował|za=me=dy=to=wał|7 +zamilkł|za=milkł|7 +zamknięty|za=mknię=ty|7 +zapamiętałością|za=pa=mię=ta=ło=ścią|7 +zapisu|za=pi=su|7 +zapodział|za=po=dział|7 +zapomnieli|za=po=mnie=li|7 +zapraszać|za=pra=szać|7 +zapłacić|za=pła=cić|7 +zarzewie|za=rze=wie|7 +zarzewiem|za=rze=wiem|7 +zasiadł|za=siadł|7 +zasnute|za=snu=te|7 +zasnęła|za=snę=ła|7 +zawodziły|za=wo=dzi=ły|7 +zaśpiewał|za=śpie=wał|7 +zbożem|zbo=żem|7 +zdechła|zde=chła|7 +zdrowy|zdro=wy|7 +zdumienia|zdu=mie=nia|7 +zdążyć|zdą=żyć|7 +zdążyła|zdą=ży=ła|7 +zerwały|ze=rwa=ły|7 +zgodził|zgo=dził|7 +ziarnem|ziar=nem|7 +zipiąc|zi=piąc|7 +zmartwiała|zmar=twia=ła|7 +zmarło|zmar=ło|7 +zmówić|zmó=wić|7 +znaczy|zna=czy|7 +znikąd|zni=kąd|7 +zrywała|zry=wa=ła|7 +zwyczajem|zwy=cza=jem|7 +złocie|zło=cie|7 +złodzieja|zło=dzie=ja|7 +ławkach|ław=kach|7 +łaziła|ła=zi=ła|7 +łąkach|łą=kach|7 +łęgach|łę=gach|7 +ścianach|ścia=nach|7 +Ścierwa|Ścier=wa|7 +ścisnął|ści=snął|7 +ścisnęło|ści=snę=ło|7 +ściółkę|ściół=kę|7 +śledzia|śle=dzia|7 +śmigały|śmi=ga=ły|7 +świecami|świe=ca=mi|7 +świeżo|świe=żo|7 +święci|świę=ci|7 +żałowała|ża=ło=wa=ła|7 +żałością|ża=ło=ścią|7 +życiem|ży=ciem|7 +Żydzie|Ży=dzie|7 +żywota|ży=wo=ta|7 +alkierzu|al=kie=rzu|6 +Antkowie|Ant=ko=wie|6 +Antoni|An=to=ni|6 +baczysz|ba=czysz|6 +barana|ba=ra=na|6 +baraszkował|ba=rasz=ko=wał|6 +baraszkowały|ba=rasz=ko=wa=ły|6 +bekiem|be=kiem|6 +bełkotem|beł=ko=tem|6 +biegali|bie=ga=li|6 +bierzcie|bierz=cie|6 +bieżyj|bie=żyj|6 +bijące|bi=ją=ce|6 +bijącym|bi=ją=cym|6 +bochen|bo=chen|6 +bogactwa|bo=gac=twa|6 +bogacze|bo=ga=cze|6 +bolesne|bo=le=sne|6 +borowego|bo=ro=we=go|6 +Borynowego|Bo=ry=no=we=go|6 +Borynowy|Bo=ry=no=wy|6 +Borynowym|Bo=ry=no=wym|6 +brzaskami|brza=ska=mi|6 +brzegu|brze=gu|6 +brzęki|brzę=ki|6 +buchało|bu=cha=ło|6 +burych|bu=rych|6 +bywały|by=wa=ły|6 +byłaby|by=ła=by|6 +Byście|By=ście|6 +Całkiem|Cał=kiem|6 +całowali|ca=ło=wa=li|6 +całowała|ca=ło=wa=ła|6 +cepami|ce=pa=mi|6 +Chciał|Chciał|6 +chlebem|chle=bem|6 +chlipanie|chli=pa=nie|6 +chmurzyskami|chmu=rzy=ska=mi|6 +chmurą|chmu=rą|6 +chodźmy|chodź=my|6 +Chodźta|Chodź=ta|6 +chorej|cho=rej|6 +choćbyś|choć=byś|6 +Chrystus|Chry=stus|6 +chrzęstem|chrzę=stem|6 +chwalił|chwa=lił|6 +chwasty|chwa=sty|6 +chwytając|chwy=ta=jąc|6 +chłodu|chło=du|6 +chłopskich|chłop=skich|6 +cielak|cie=lak|6 +ciemny|ciem=ny|6 +cieple|cie=ple|6 +ciepnął|ciep=nął|6 +ciepłym|cie=płym|6 +cieszyć|cie=szyć|6 +cieszył|cie=szył|6 +ciołak|cio=łak|6 +ciołka|cioł=ka|6 +ciurkiem|ciur=kiem|6 +ciągnąć|cią=gnąć|6 +ciągłe|cią=głe|6 +ciężkimi|cięż=ki=mi|6 +ciżbie|ciż=bie|6 +coście|co=ście|6 +czasownika|cza=sow=ni=ka|6 +czegóż|cze=góż|6 +czołem|czo=łem|6 +czujące|czu=ją=ce|6 +czynnością|czyn=no=ścią|6 +człowiecze|czło=wie=cze|6 +córuchno|có=ruch=no|6 +dachach|da=chach|6 +dalekim|da=le=kim|6 +dalekościach|da=le=ko=ściach|6 +dawali|da=wa=li|6 +deliberował|de=li=be=ro=wał|6 +diabli|dia=bli|6 +dlatego|dla=te=go|6 +dobremu|do=bre=mu|6 +dobytek|do=by=tek|6 +dochtory|do=chto=ry|6 +doczekać|do=cze=kać|6 +dojrzały|doj=rza=ły|6 +dokładnie|do=kład=nie|6 +dopraszam|do=pra=szam|6 +dostanie|do=sta=nie|6 +dosłyszawszy|do=sły=szaw=szy|6 +dowodził|do=wo=dził|6 +dołoży|do=ło=ży|6 +drapać|dra=pać|6 +dreszcz|dreszcz|6 +drewek|dre=wek|6 +drgały|drga=ły|6 +drzewina|drze=wi=na|6 +dróżce|dróż=ce|6 +drżące|drżą=ce|6 +dwojaki|dwo=ja=ki|6 +dygotała|dy=go=ta=ła|6 +dygotało|dy=go=ta=ło|6 +dygotem|dy=go=tem|6 +dymach|dy=mach|6 +działach|dzia=łach|6 +działy|dzia=ły|6 +dzieciach|dzie=ciach|6 +dziedzicowy|dzie=dzi=co=wy|6 +dziobem|dzio=bem|6 +dzioby|dzio=by|6 +dziwna|dziw=na|6 +Dziwowali|Dzi=wo=wa=li|6 +dzwoniła|dzwo=ni=ła|6 +długachne|dłu=gach=ne|6 +dłuższą|dłuż=szą|6 +dłużył|dłu=żył|6 +dźwigając|dźwi=ga=jąc|6 +farbami|far=ba=mi|6 +gadanie|ga=da=nie|6 +galopem|ga=lo=pem|6 +garnek|gar=nek|6 +gałęziach|ga=łę=ziach|6 +gałęzią|ga=łę=zią|6 +giezło|gie=zło|6 +gorszego|gor=sze=go|6 +gorzko|gorz=ko|6 +gorącymi|go=rą=cy=mi|6 +gospodarki|go=spo=dar=ki|6 +gospodarzami|go=spo=da=rza=mi|6 +gospodarzowi|go=spo=da=rzo=wi|6 +gospodyń|go=spo=dyń|6 +gołębi|go=łę=bi|6 +gościniec|go=ści=niec|6 +grobli|gro=bli|6 +grochem|gro=chem|6 +gromadę|gro=ma=dę|6 +gromnicę|grom=ni=cę|6 +groszem|gro=szem|6 +groziła|gro=zi=ła|6 +groźby|groź=by|6 +groźna|groź=na|6 +grubachne|gru=bach=ne|6 +grusze|gru=sze|6 +grzeszne|grzesz=ne|6 +gwarząc|gwa=rząc|6 +gwiezdne|gwiezd=ne|6 +gąsków|gą=sków|6 +gęsiego|gę=sie=go|6 +gęstniał|gęst=niał|6 +gęstwa|gę=stwa|6 +głaskać|gła=skać|6 +głaskał|gła=skał|6 +głupiego|głu=pie=go|6 +Hameryki|Ha=me=ry=ki|6 +hamował|ha=mo=wał|6 +hardość|har=dość|6 +historię|hi=sto=rię|6 +idziecie|idzie=cie|6 +indory|in=do=ry|6 +izdebki|iz=deb=ki|6 +jabłek|ja=błek|6 +Jagusina|Ja=gu=si=na|6 +Jankla|Jan=kla|6 +jasnością|ja=sno=ścią|6 +jazgoty|ja=zgo=ty|6 +Jaśkiem|Jaś=kiem|6 +jaśniały|ja=śnia=ły|6 +jechali|je=cha=li|6 +jedzie|je=dzie|6 +Jeszczek|Jesz=czek|6 +Jezusowego|Je=zu=so=we=go|6 +jeździli|jeź=dzi=li|6 +jeżeli|je=że=li|6 +Jędrzycha|Ję=drzy=cha|6 +jękliwe|ję=kli=we|6 +jękliwym|ję=kli=wym|6 +języka|ję=zy=ka|6 +kancelarią|kan=ce=la=rią|6 +kapały|ka=pa=ły|6 +kapocie|ka=po=cie|6 +karczmach|karcz=mach|6 +kawale|ka=wa=le|6 +kawałki|ka=wał=ki|6 +Kiedyż|Kie=dyż|6 +kieliszka|kie=lisz=ka|6 +kieszeni|kie=sze=ni|6 +kiełbasa|kieł=ba=sa|6 +kiełbasę|kieł=ba=sę|6 +kijaszek|ki=ja=szek|6 +klęczała|klę=cza=ła|6 +kobieto|ko=bie=to|6 +kobyłę|ko=by=łę|6 +kochana|ko=cha=na|6 +kokoty|ko=ko=ty|6 +komisarza|ko=mi=sa=rza|6 +kompanią|kom=pa=nią|6 +koniecznie|ko=niecz=nie|6 +konstrukcja|kon=struk=cja|6 +Kozłowej|Ko=zło=wej|6 +kołujący|ko=łu=ją=cy|6 +końcówką|koń=ców=ką|6 +końskie|koń=skie|6 +kościołowi|ko=ścio=ło=wi|6 +Kościół|Ko=ściół|6 +krewniaki|krew=nia=ki|6 +krewniaków|krew=nia=ków|6 +krzepciej|krzep=ciej|6 +krzyczą|krzy=czą|6 +krzyżu|krzy=żu|6 +królach|kró=lach|6 +kręcić|krę=cić|6 +kręciły|krę=ci=ły|6 +krętanina|krę=ta=ni=na|6 +kumami|ku=ma=mi|6 +kuntentność|kun=tent=ność|6 +kuropatki|ku=ro=pat=ki|6 +Kużden|Kuż=den|6 +kwiliły|kwi=li=ły|6 +lament|la=ment|6 +lasach|la=sach|6 +leciało|le=cia=ło|6 +leniwie|le=ni=wie|6 +lepszego|lep=sze=go|6 +lepszy|lep=szy|6 +lewentarz|le=wen=tarz|6 +leżących|le=żą=cych|6 +Lipcami|Lip=ca=mi|6 +Maciejowi|Ma=cie=jo=wi|6 +mamrotać|mam=ro=tać|6 +markotnie|mar=kot=nie|6 +matczynych|mat=czy=nych|6 +miarkował|miar=ko=wał|6 +miarkujcie|miar=kuj=cie|6 +migotał|mi=go=tał|6 +milcząca|mil=czą=ca|6 +mocniejszy|moc=niej=szy|6 +modlić|mo=dlić|6 +modliła|mo=dli=ła|6 +mrowiły|mro=wi=ły|6 +mruczała|mru=cza=ła|6 +mrówki|mrów=ki|6 +musiałem|mu=sia=łem|6 +musisz|mu=sisz|6 +mówiłam|mó=wi=łam|6 +młynarczyk|mły=nar=czyk|6 +nabożeństwa|na=bo=żeń=stwa|6 +nabrzmiały|na=brzmia=ły|6 +nadchodził|nad=cho=dził|6 +naokół|na=okół|6 +napisane|na=pi=sa=ne|6 +naprawdę|na=praw=dę|6 +narodził|na=ro=dził|6 +Nastce|Na=st=ce|6 +Nastusię|Na=stu=się|6 +naszczekiwał|na=szcze=ki=wał|6 +naszych|na=szych|6 +natknęła|na=tknę=ła|6 +niczegój|ni=cze=gój|6 +niczemu|ni=cze=mu|6 +niechże|niech=że|6 +niedzieli|nie=dzie=li|6 +niektóry|nie=któ=ry|6 +nieletko|nie=let=ko|6 +niemocy|nie=mo=cy|6 +niepowstrzymanie|nie=po=wstrzy=ma=nie|6 +nieprzyjacioły|nie=przy=ja=cio=ły|6 +nierad|nie=rad|6 +nierada|nie=ra=da|6 +niesie|nie=sie|6 +niesprawiedliwość|nie=spra=wie=dli=wość|6 +niesąc|nie=sąc|6 +nieustępliwą|nie=ustę=pli=wą|6 +niezadługo|nie=za=dłu=go|6 +Niezgorzej|Nie=zgo=rzej|6 +niosąc|nio=sąc|6 +niosły|nio=sły|6 +obaczył|oba=czył|6 +obejmował|obej=mo=wał|6 +oberwał|obe=rwał|6 +objaśniał|ob=ja=śniał|6 +objaśniała|ob=ja=śnia=ła|6 +obleczenia|ob=le=cze=nia|6 +obleczeniu|ob=le=cze=niu|6 +obmierzły|ob=mier=z=ły|6 +obrazem|ob=ra=zem|6 +obrony|obro=ny|6 +obzierać|obzie=rać|6 +ochfiarowała|ochfia=ro=wa=ła|6 +ociężale|ocię=ża=le|6 +oczekując|ocze=ku=jąc|6 +oddała|od=da=ła|6 +oddech|od=dech|6 +oddechy|od=de=chy|6 +odebrał|ode=brał|6 +odeszli|ode=szli|6 +odmówienie|od=mó=wie=nie|6 +odpowie|od=po=wie|6 +odsunął|od=su=nął|6 +odwracał|od=wra=cał|6 +odwracała|od=wra=ca=ła|6 +oganiając|oga=nia=jąc|6 +ogiery|ogie=ry|6 +ogniste|ogni=ste|6 +ogrodzie|ogro=dzie|6 +ojcowego|oj=co=we=go|6 +okrutnie|okrut=nie|6 +okręcił|okrę=cił|6 +omacku|omac=ku|6 +opadła|opa=dła|6 +opierał|opie=rał|6 +opowiadając|opo=wia=da=jąc|6 +opowiedzieć|opo=wie=dzieć|6 +oprzytomniał|oprzy=tom=niał|6 +organistą|or=ga=ni=stą|6 +Organiścina|Or=ga=ni=ści=na|6 +ostrzej|ostrzej|6 +otwarcie|otwar=cie|6 +otwierać|otwie=rać|6 +otwierało|otwie=ra=ło|6 +otwierały|otwie=ra=ły|6 +ozorach|ozo=rach|6 +ozwarł|ozwarł|6 +oścież|oścież|6 +ożenić|oże=nić|6 +paciorków|pa=cior=ków|6 +padała|pa=da=ła|6 +palcem|pal=cem|6 +pamięta|pa=mię=ta|6 +Panajezusowe|Pa=na=je=zu=so=we|6 +panuje|pa=nu=je|6 +papierosy|pa=pie=ro=sy|6 +papieru|pa=pie=ru|6 +pasieki|pa=sie=ki|6 +paśniki|pa=śni=ki|6 +pedział|pe=dział|6 +piekle|pie=kle|6 +piesneczki|pie=snecz=ki|6 +Pietrkiem|Pietr=kiem|6 +Pietrusia|Pie=tru=sia|6 +pieściwie|pie=ści=wie|6 +pieśni|pie=śni|6 +pilnuje|pil=nu=je|6 +pobili|po=bi=li|6 +pochowku|po=chow=ku|6 +pociecha|po=cie=cha|6 +pociechę|po=cie=chę|6 +pociągając|po=cią=ga=jąc|6 +pociągnęła|po=cią=gnę=ła|6 +poczęstunek|po=czę=stu=nek|6 +podjeść|pod=jeść|6 +Podlesia|Pod=le=sia|6 +podmurówki|pod=mu=rów=ki|6 +Podniesła|Pod=nie=sła|6 +podniesło|pod=nie=sło|6 +Podniósł|Pod=niósł|6 +podsuwając|pod=su=wa=jąc|6 +podszedł|pod=szedł|6 +poduszki|po=dusz=ki|6 +podzielić|po=dzie=lić|6 +pogadywać|po=ga=dy=wać|6 +pogrzebie|po=grze=bie|6 +pojechała|po=je=cha=ła|6 +pojękiwał|po=ję=ki=wał|6 +pokazało|po=ka=za=ło|6 +pokazały|po=ka=za=ły|6 +polśniewał|po=lśnie=wał|6 +polśniewały|po=lśnie=wa=ły|6 +pomdlałe|po=mdla=łe|6 +pomieścić|po=mie=ścić|6 +Poniechajcie|Po=nie=chaj=cie|6 +poniektórych|po=nie=któ=rych|6 +poniesły|po=nie=sły|6 +ponieśli|po=nie=śli|6 +ponosił|po=no=sił|6 +popuszczał|po=pusz=czał|6 +popłakiwała|po=pła=ki=wa=ła|6 +poredzili|po=re=dzi=li|6 +porykiwania|po=ry=ki=wa=nia|6 +posiał|po=siał|6 +postawili|po=sta=wi=li|6 +postało|po=sta=ło|6 +postronki|po=stron=ki|6 +postękiwał|po=stę=ki=wał|6 +poszumem|po=szu=mem|6 +posłucha|po=słu=cha|6 +potoczył|po=to=czył|6 +powiadaj|po=wia=daj|6 +powinna|po=win=na|6 +pozdrowienia|po=zdro=wie=nia|6 +pozwolili|po=zwo=li=li|6 +pościel|po=ściel|6 +pośpiechu|po=śpie=chu|6 +pożogą|po=żo=gą|6 +pożyczyć|po=ży=czyć|6 +prawdziwie|praw=dzi=wie|6 +prawdziwy|praw=dzi=wy|6 +prawej|pra=wej|6 +prosząc|pro=sząc|6 +prosząco|pro=szą=co|6 +prośby|proś=by|6 +Pryczek|Pry=czek|6 +przeciwił|prze=ciw=ił|6 +przeciwiła|prze=ciw=i=ła|6 +przeciągle|prze=cią=gle|6 +przedostawać|prze=do=sta=wać|6 +przekradać|prze=kra=dać|6 +przemawiał|prze=ma=wiał|6 +przemieni|prze=mie=ni|6 +przemówił|prze=mó=wił|6 +przeniósł|prze=niósł|6 +przepadła|prze=pa=dła|6 +Przepili|Prze=pi=li|6 +przepomnieć|prze=po=mnieć|6 +przepuści|prze=pu=ści|6 +przepychać|prze=py=chać|6 +przerażających|prze=ra=ża=ją=cych|6 +przeróżnych|prze=róż=nych|6 +przetaczało|prze=ta=cza=ło|6 +przeto|prze=to|6 +przetrzymać|prze=trzy=mać|6 +przewalała|prze=wa=la=ła|6 +przewalały|prze=wa=la=ły|6 +prześpiegi|prze=śpie=gi|6 +przychodzę|przy=cho=dzę|6 +przycichła|przy=ci=chła|6 +przyciszać|przy=ci=szać|6 +przyginał|przy=gi=nał|6 +przygiął|przy=giął|6 +przyjacielstwie|przy=ja=ciel=stwie|6 +przyjacioły|przy=ja=cio=ły|6 +przyjaźnie|przy=jaź=nie|6 +Przyjdzie|Przyj=dzie|6 +przyjrzał|przyj=rzał|6 +przykazywał|przy=ka=zy=wał|6 +przyklęknął|przy=klęk=nął|6 +przymierzać|przy=mie=rzać|6 +przyniesie|przy=nie=sie|6 +przyniesę|przy=nie=sę|6 +przyniewalać|przy=nie=wa=lać|6 +przyodziane|przy=odzia=ne|6 +przyodziać|przy=odziać|6 +przyodziała|przy=odzia=ła|6 +przypiecku|przy=piec=ku|6 +przypominał|przy=po=mi=nał|6 +przystał|przy=stał|6 +przystało|przy=sta=ło|6 +przysłoniła|przy=sło=ni=ła|6 +przytrafić|przy=tra=fić|6 +przytwierdzał|przy=twier=dzał|6 +przytwierdzała|przy=twier=dza=ła|6 +przywarł|przy=warł|6 +przywierała|przy=wie=ra=ła|6 +przyziemnych|przy=ziem=nych|6 +przyłożył|przy=ło=żył|6 +Przyłęka|Przy=łę=ka|6 +przędziwem|przę=dzi=wem|6 +przędzy|przę=dzy|6 +próbując|pró=bu=jąc|6 +psiakrew|psia=krew|6 +pszczelnym|psz=czel=nym|6 +pszczoły|psz=czo=ły|6 +pszenicy|psze=ni=cy|6 +ptaszka|ptasz=ka|6 +ptaszków|ptasz=ków|6 +puszcza|pusz=cza|6 +puszczały|pusz=cza=ły|6 +puszczę|pusz=czę|6 +puściły|pu=ści=ły|6 +pyskiem|py=skiem|6 +pyskuje|py=sku=je|6 +płacił|pła=cił|6 +płakali|pła=ka=li|6 +płakały|pła=ka=ły|6 +płonęły|pło=nę=ły|6 +Płoszki|Płosz=ki|6 +Płoszkową|Płosz=ko=wą|6 +płynie|pły=nie|6 +robaki|ro=ba=ki|6 +robotach|ro=bo=tach|6 +rodzonego|ro=dzo=ne=go|6 +rogate|ro=ga=te|6 +rozchodzili|roz=cho=dzi=li|6 +rozchodził|roz=cho=dził|6 +rozeszły|ro=ze=szły|6 +rozgorzałe|roz=go=rza=łe|6 +rozgorzałymi|roz=go=rza=ły=mi|6 +rozkołysane|roz=ko=ły=sa=ne|6 +rozlewając|roz=le=wa=jąc|6 +Rozmyślała|Roz=my=śla=ła|6 +roznosiło|roz=no=si=ło|6 +rozpaczliwie|roz=pacz=li=wie|6 +rozpaczy|roz=pa=czy|6 +rozpaczą|roz=pa=czą|6 +rozpalonym|roz=pa=lo=nym|6 +rozpozna|roz=po=zna|6 +rozpłakała|roz=pła=ka=ła|6 +roztrzęsiona|roz=trzę=sio=na|6 +rozwodząc|roz=wo=dząc|6 +ruchem|ru=chem|6 +ruchomą|ru=cho=mą|6 +ruszali|ru=sza=li|6 +rzeczach|rze=czach|6 +rzewnie|rzew=nie|6 +rzucali|rzu=ca=li|6 +rzutem|rzu=tem|6 +rzędami|rzę=da=mi|6 +różnościach|róż=no=ściach|6 +rączków|rącz=ków|6 +sadami|sa=da=mi|6 +sadzić|sa=dzić|6 +saniach|sa=niach|6 +saniami|sa=nia=mi|6 +schodzić|scho=dzić|6 +schować|scho=wać|6 +schowała|scho=wa=ła|6 +sennym|sen=nym|6 +siedzeniu|sie=dze=niu|6 +siedząca|sie=dzą=ca|6 +sieroto|sie=ro=to|6 +silniej|sil=niej|6 +siwiły|si=wi=ły|6 +sięgając|się=ga=jąc|6 +skamlała|skam=la=ła|6 +skamlały|skam=la=ły|6 +skarżąc|skar=żąc|6 +skowronkowe|skow=ron=ko=we|6 +skowytem|sko=wy=tem|6 +skończyć|skoń=czyć|6 +skończą|skoń=czą|6 +skropił|skro=pił|6 +skrzypkach|skrzyp=kach|6 +skrzyły|skrzy=ły|6 +skręcał|skrę=cał|6 +skręcie|skrę=cie|6 +smakuje|sma=ku=je|6 +smutki|smut=ki|6 +smutkiem|smut=kiem|6 +smutno|smut=no|6 +smutny|smut=ny|6 +Sołtys|Soł=tys|6 +spadła|spa=dła|6 +spluwał|splu=wał|6 +spoglądali|spo=glą=da=li|6 +spoglądać|spo=glą=dać|6 +spokoić|spo=ko=ić|6 +spokojności|spo=koj=no=ści|6 +sponiewierał|spo=nie=wie=rał|6 +spostrzegła|spo=strze=gła|6 +spotkać|spo=tkać|6 +spozierać|spo=zie=rać|6 +Sprawa|Spra=wa|6 +sprawiedliwie|spra=wie=dli=wie|6 +Sprawiedliwie|Spra=wie=dli=wie|6 +sprawiedliwością|spra=wie=dli=wo=ścią|6 +sprawić|spra=wić|6 +sprawię|spra=wię|6 +sprawił|spra=wił|6 +sprawnie|spraw=nie|6 +spuścił|spu=ścił|6 +spólnie|spól=nie|6 +spółki|spół=ki|6 +spółkę|spół=kę|6 +spłaty|spła=ty|6 +Stachowi|Sta=cho=wi|6 +stajanie|sta=ja=nie|6 +Starszy|Star=szy|6 +staruchy|sta=ru=chy|6 +stawiać|sta=wiać|6 +sterczały|ster=cza=ły|6 +stodołami|sto=do=ła=mi|6 +stodołą|sto=do=łą|6 +stodół|sto=dół|6 +stojące|sto=ją=ce|6 +stojących|sto=ją=cych|6 +stosunku|sto=sun=ku|6 +stołem|sto=łem|6 +strata|stra=ta|6 +strażnik|straż=nik|6 +straży|stra=ży|6 +strwożeni|str=wo=że=ni|6 +strzech|strzech|6 +stróżujące|stró=żu=ją=ce|6 +studnie|stud=nie|6 +studnią|stud=nią|6 +szarej|sza=rej|6 +szarości|sza=ro=ści|6 +szarzały|sza=rza=ły|6 +szczerym|szcze=rym|6 +szczękały|szczę=ka=ły|6 +szerokim|sze=ro=kim|6 +szkodę|szko=dę|6 +szkołą|szko=łą|6 +szmatami|szma=ta=mi|6 +sznury|sznu=ry|6 +szumnie|szum=nie|6 +szydziła|szy=dzi=ła|6 +szykowali|szy=ko=wa=li|6 +Szymkiem|Szym=kiem|6 +szynkwasem|szynk=wa=sem|6 +sądach|są=dach|6 +sędzia|sę=dzia|6 +słodkich|słod=kich|6 +słodkiej|słod=kiej|6 +słodkością|słod=ko=ścią|6 +słoneczny|sło=necz=ny|6 +słuchając|słu=cha=jąc|6 +Słuchali|Słu=cha=li|6 +Słuchała|Słu=cha=ła|6 +słyszy|sły=szy|6 +tamtej|tam=tej|6 +targał|tar=gał|6 +Tereski|Te=re=ski|6 +trusia|tru=sia|6 +trzask|trzask|6 +trzewiki|trze=wi=ki|6 +trzymaj|trzy=maj|6 +tumanach|tu=ma=nach|6 +twojej|two=jej|6 +tygodnia|ty=go=dnia|6 +tylachny|ty=lach=ny|6 +tęskliwe|tę=skli=we|6 +tęsknicy|tę=sk=ni=cy|6 +tęsknie|tę=sk=nie|6 +tęskności|tę=sk=no=ści|6 +tęskność|tę=sk=ność|6 +tłucze|tłu=cze|6 +tłumaczyć|tłu=ma=czyć|6 +tłusty|tłu=sty|6 +ubranie|ubra=nie|6 +uchwalić|uchwa=lić|6 +uciecha|ucie=cha|6 +uciekała|ucie=ka=ła|6 +udźwignie|udźwi=gnie|6 +ukrzywdził|ukrzyw=dził|6 +Ulisia|Uli=sia|6 +uniosła|unio=sła|6 +upadek|upa=dek|6 +uparcie|upar=cie|6 +upominał|upo=mi=nał|6 +uradzi|ura=dzi|6 +uroczyste|uro=czy=ste|6 +urodny|urod=ny|6 +urodzić|uro=dzić|6 +Urzędnik|Urzęd=nik|6 +ustawili|usta=wi=li|6 +utrapień|utra=pień|6 +utrudzony|utru=dzo=ny|6 +utrzymanie|utrzy=ma=nie|6 +uwidzi|uwi=dzi|6 +uwijał|uwi=jał|6 +ułapiło|uła=pi=ło|6 +Wachników|Wach=ni=ków|6 +wagował|wa=go=wał|6 +ważnie|waż=nie|6 +Wczoraj|Wczo=raj|6 +wczorajszej|wczo=raj=szej|6 +weselej|we=se=lej|6 +weselne|we=sel=ne|6 +Widziałem|Wi=dzia=łem|6 +widłami|wi=dła=mi|6 +wieczornych|wie=czor=nych|6 +wiedziały|wie=dzia=ły|6 +wielgachnych|wiel=gach=nych|6 +wielkich|wiel=kich|6 +wierząc|wie=rząc|6 +wilgotne|wil=got=ne|6 +większość|więk=szość|6 +wokoło|wo=ko=ło|6 +wołają|wo=ła=ją|6 +wpatrzona|wpa=trzo=na|6 +wpierając|wpie=ra=jąc|6 +wracać|wra=cać|6 +wraził|wra=ził|6 +wrzasku|wrza=sku|6 +wrzawie|wrza=wie|6 +wrzawy|wrza=wy|6 +wrzeszczeli|wrzesz=cze=li|6 +wrzeszczeć|wrzesz=czeć|6 +wrzątek|wrzą=tek|6 +wsadził|wsa=dził|6 +wstąpiła|wstą=pi=ła|6 +Wszyćko|Wszyć=ko|6 +wybiegając|wy=bie=ga=jąc|6 +wybojach|wy=bo=jach|6 +wybrała|wy=bra=ła|6 +wybuchnęła|wy=buch=nę=ła|6 +wychodziły|wy=cho=dzi=ły|6 +wydała|wy=da=ła|6 +wydało|wy=da=ło|6 +wykrztusiła|wy=krztu=si=ła|6 +wykrzykiwała|wy=krzy=ki=wa=ła|6 +wykręcał|wy=krę=cał|6 +wylazł|wy=lazł|6 +wynosili|wy=no=si=li|6 +wynosić|wy=no=sić|6 +wypadła|wy=pa=dła|6 +wypatruje|wy=pa=tru=je|6 +wypatrując|wy=pa=tru=jąc|6 +wyprawiała|wy=pra=wia=ła|6 +wyprowadził|wy=pro=wa=dził|6 +wypuścił|wy=pu=ścił|6 +wypytywał|wy=py=ty=wał|6 +wyrzekając|wy=rze=ka=jąc|6 +wyschła|wy=schła|6 +wysiedzieć|wy=sie=dzieć|6 +wysokościach|wy=so=ko=ściach|6 +wystraszona|wy=stra=szo=na|6 +wyszły|wy=szły|6 +wytrzyma|wy=trzy=ma|6 +wywartych|wy=war=tych|6 +wywiódł|wy=wiódł|6 +wywodzić|wy=wo=dzić|6 +wywrzeć|wy=wrzeć|6 +wyzdrowiał|wy=zdro=wiał|6 +Wyście|Wy=ście|6 +wyżalić|wy=ża=lić|6 +wzburzona|wzbu=rzo=na|6 +wzburzone|wzbu=rzo=ne|6 +wzgórze|wzgó=rze|6 +Wzieni|Wzie=ni|6 +Wzięła|Wzię=ła|6 +wątpia|wąt=pia|6 +węgieł|wę=gieł|6 +węzełek|wę=ze=łek|6 +własne|wła=sne|6 +włosach|wło=sach|6 +zabawiał|za=ba=wiał|6 +zabierał|za=bie=rał|6 +zabite|za=bi=te|6 +zachodził|za=cho=dził|6 +zachorzał|za=cho=rzał|6 +zachrapał|za=chra=pał|6 +zaczerwienione|za=czer=wie=nio=ne|6 +zaczynało|za=czy=na=ło|6 +zadyszana|za=dy=sza=na|6 +zadzie|za=dzie|6 +zagadać|za=ga=dać|6 +zagadując|za=ga=du=jąc|6 +zajadlej|za=ja=dlej|6 +zajrzy|zaj=rzy|6 +zajrzyj|zaj=rzyj|6 +zajączka|za=jącz=ka|6 +zakatrupię|za=ka=tru=pię|6 +zakolebały|za=ko=le=ba=ły|6 +zakładał|za=kła=dał|6 +zaległo|za=le=gło|6 +zamamrotał|za=mam=ro=tał|6 +zamknął|za=mknął|6 +zamrze|za=mrze|6 +zapachem|za=pa=chem|6 +zapadała|za=pa=da=ła|6 +zapadł|za=padł|6 +zapalili|za=pa=li=li|6 +zapartym|za=par=tym|6 +zapatrzył|za=pa=trzył|6 +zapowiadał|za=po=wia=dał|6 +zapowiedzi|za=po=wie=dzi|6 +zaprzestał|za=prze=stał|6 +zapłacił|za=pła=cił|6 +zarobić|za=ro=bić|6 +zaroiły|za=ro=iły|6 +zarzewia|za=rze=wia|6 +zasiedli|za=sie=dli|6 +zaszczekał|za=szcze=kał|6 +zawarczał|za=war=czał|6 +zawarty|za=war=ty|6 +zawiał|za=wiał|6 +Zawrzyj|Za=wrzyj|6 +zawziął|za=wziął|6 +zawziętości|za=wzię=to=ści|6 +Zawżdy|Za=wż=dy|6 +zaśpiewali|za=śpie=wa=li|6 +zaśpiewała|za=śpie=wa=ła|6 +zbierało|zbie=ra=ło|6 +zdawało|zda=wa=ło|6 +zdrowe|zdro=we|6 +zdumiał|zdu=miał|6 +zebrali|ze=bra=li|6 +zebranie|ze=bra=nie|6 +zechce|ze=chce|6 +zestrachane|ze=stra=cha=ne|6 +zeszedł|ze=szedł|6 +zeszli|ze=szli|6 +zeszły|ze=szły|6 +zginęło|zgi=nę=ło|6 +zgodzić|zgo=dzić|6 +zgromiła|zgro=mi=ła|6 +zielonym|zie=lo=nym|6 +zieloną|zie=lo=ną|6 +zięcia|zię=cia|6 +zjadliwie|zja=dli=wie|6 +zmartwienie|zmar=twie=nie|6 +znalazła|zna=la=zła|6 +zobaczy|zo=ba=czy|6 +zostanie|zo=sta=nie|6 +zrozumiał|zro=zu=miał|6 +zrywały|zry=wa=ły|6 +zwlókł|zwlókł|6 +zwoził|zwo=ził|6 +zwyczajny|zwy=czaj=ny|6 +złotem|zło=tem|6 +łajdusy|łaj=du=sy|6 +łańcuchem|łań=cu=chem|6 +łańcuchu|łań=cu=chu|6 +ścierpieć|ścier=pieć|6 +ścieżaj|ście=żaj|6 +ściszone|ści=szo=ne|6 +śledzie|śle=dzie|6 +śmiałości|śmia=ło=ści|6 +śmielej|śmie=lej|6 +śmiercią|śmier=cią|6 +śmiertelne|śmier=tel=ne|6 +śpiewa|śpie=wa|6 +śpiewają|śpie=wa=ją|6 +śpiewki|śpiew=ki|6 +śpiewów|śpie=wów|6 +świadkach|świad=kach|6 +światełka|świa=teł=ka|6 +światu|świa=tu|6 +światłość|świa=tłość|6 +świetle|świe=tle|6 +świątecznie|świą=tecz=nie|6 +święconego|świę=co=ne=go|6 +Święta|Świę=ta|6 +świętami|świę=ta=mi|6 +świętości|świę=to=ści|6 +źrebak|źre=bak|6 +żarliwie|żar=li=wie|6 +żartował|żar=to=wał|6 +żałosną|ża=ło=sną|6 +Żydowi|Ży=do=wi|6 +żółtym|żół=tym|6 +żółtymi|żół=ty=mi|6 +ambonie|am=bo=nie|5 +Ambroży|Am=bro=ży|5 +baczycie|ba=czy=cie|5 +Balcerków|Bal=cer=ków|5 +beczkę|becz=kę|5 +bezładnie|bez=ład=nie|5 +bełkot|beł=kot|5 +białego|bia=łe=go|5 +biedny|bied=ny|5 +biedotę|bie=do=tę|5 +bieganina|bie=ga=ni=na|5 +biegał|bie=gał|5 +biegała|bie=ga=ła|5 +biegnącej|bie=gną=cej|5 +biegnącą|bie=gną=cą|5 +bielmem|biel=mem|5 +bielone|bie=lo=ne|5 +bijatyki|bi=ja=ty=ki|5 +bladych|bla=dych|5 +bolenia|bo=le=nia|5 +bolesna|bo=le=sna|5 +boskiego|bo=skie=go|5 +brewiarz|bre=wiarz|5 +brzemię|brze=mię|5 +brzózek|brzó=zek|5 +bródkę|bród=kę|5 +budziła|bu=dzi=ła|5 +bujnie|buj=nie|5 +burknął|burk=nął|5 +butach|bu=tach|5 +butelkę|bu=tel=kę|5 +bydlątko|by=dląt=ko|5 +bydłem|by=dłem|5 +Bylicą|By=li=cą|5 +Byliście|By=li=ście|5 +Bójcie|Bój=cie|5 +błyskawica|bły=ska=wi=ca|5 +błyszczący|błysz=czą=cy|5 +całunkami|ca=łun=ka=mi|5 +Chcesz|Chcesz|5 +Chodźmy|Chodź=my|5 +chorobę|cho=ro=bę|5 +chorzał|cho=rzał|5 +chroniąc|chro=niąc|5 +chrzcie|chrzcie|5 +chudziaczek|chu=dzia=czek|5 +chwaci|chwa=ci|5 +chwała|chwa=ła|5 +chwile|chwi=le|5 +chwilą|chwi=lą|5 +chwyciła|chwy=ci=ła|5 +chybciej|chyb=ciej|5 +chybcikiem|chyb=ci=kiem|5 +chyliły|chy=li=ły|5 +chylące|chy=lą=ce|5 +chytał|chy=tał|5 +chłodny|chłod=ny|5 +chłopakom|chło=pa=kom|5 +chłopakowi|chło=pa=ko=wi|5 +chłopski|chłop=ski|5 +chłopskiego|chłop=skie=go|5 +chłopskiej|chłop=skiej|5 +ciekawość|cie=ka=wość|5 +ciemnica|ciem=ni=ca|5 +ciepła|cie=pła|5 +cieszy|cie=szy|5 +ciągnąc|cią=gnąc|5 +Cięgiem|Cię=giem|5 +ciężkiego|cięż=kie=go|5 +cmentarza|cmen=ta=rza|5 +cudzych|cu=dzych|5 +cudzym|cu=dzym|5 +czająco|cza=ją=co|5 +czarny|czar=ny|5 +czarnymi|czar=ny=mi|5 +czasach|cza=sach|5 +czekam|cze=kam|5 +czekanie|cze=ka=nie|5 +czekałam|cze=ka=łam|5 +czekały|cze=ka=ły|5 +czemuś|cze=muś|5 +czemuż|cze=muż|5 +czerwona|czer=wo=na|5 +czworakach|czwo=ra=kach|5 +czynili|czy=ni=li|5 +czyniły|czy=ni=ły|5 +czynności|czyn=no=ści|5 +czysto|czy=sto|5 +czyścu|czy=ś=cu|5 +człowieczy|czło=wie=czy|5 +dalach|da=lach|5 +dawnych|daw=nych|5 +deskami|de=ska=mi|5 +Deszcz|Deszcz|5 +diabeł|dia=beł|5 +dobrocią|do=bro=cią|5 +doczekawszy|do=cze=kaw=szy|5 +doczekał|do=cze=kał|5 +dokumentnie|do=ku=ment=nie|5 +dorznąć|do=rznąć|5 +dorzucał|do=rzu=cał|5 +dostało|do=sta=ło|5 +doszło|do=szło|5 +dotykał|do=ty=kał|5 +dowiedział|do=wie=dział|5 +drapał|dra=pał|5 +drewniany|drew=nia=ny|5 +Drugie|Dru=gie|5 +drwiąco|drwią=co|5 +Drzewa|Drze=wa|5 +drzewie|drze=wie|5 +dufnością|duf=no=ścią|5 +dufność|duf=ność|5 +dusili|du=si=li|5 +dworska|dwor=ska|5 +dworów|dwo=rów|5 +dychanie|dy=cha=nie|5 +dychał|dy=chał|5 +dygować|dy=go=wać|5 +dymiły|dy=mi=ły|5 +dziadku|dziad=ku|5 +dziadówka|dzia=dów=ka|5 +działów|dzia=łów|5 +dzieciska|dzie=ci=ska|5 +dzieciński|dzie=ciń=ski|5 +dziedzicowym|dzie=dzi=co=wym|5 +dziedziczki|dzie=dzicz=ki|5 +dziesiąte|dzie=sią=te|5 +dziesiątku|dzie=siąt=ku|5 +dzieuchom|dzieu=chom|5 +Dzieuchy|Dzieu=chy|5 +dzieuszyne|dzie=uszy=ne|5 +dzikich|dzi=kich|5 +dziwne|dziw=ne|5 +dziwnych|dziw=nych|5 +dziwnym|dziw=nym|5 +dziwowali|dzi=wo=wa=li|5 +dzwonienie|dzwo=nie=nie|5 +dzwonią|dzwo=nią|5 +dzwonić|dzwo=nić|5 +dzwonów|dzwo=nów|5 +dłuższe|dłuż=sze|5 +fajeczkę|fa=jecz=kę|5 +Filipki|Fi=lip=ki|5 +fleciku|fle=ci=ku|5 +frasunki|fra=sun=ki|5 +Fundacja|Fun=da=cja|5 +gadzina|ga=dzi=na|5 +galante|ga=lan=te|5 +gardziele|gar=dzie=le|5 +gardła|gar=dła|5 +garnczek|garn=czek|5 +garnka|garn=ka|5 +garnuszek|gar=nu=szek|5 +gałązek|ga=łą=zek|5 +gdakały|gda=ka=ły|5 +gmerał|gme=rał|5 +gniazda|gniaz=da|5 +gniazdach|gniaz=dach|5 +gniewał|gnie=wał|5 +gorsza|gor=sza|5 +gorzałce|go=rzał=ce|5 +gorącością|go=rą=co=ścią|5 +gorącość|go=rą=cość|5 +gorącym|go=rą=cym|5 +gorącą|go=rą=cą|5 +Gospodarze|Go=spo=da=rze|5 +gospodynię|go=spo=dy=nię|5 +Gołębiów|Go=łę=biów|5 +granuli|gra=nu=li|5 +grdyką|grdy=ką|5 +grobie|gro=bie|5 +groził|gro=ził|5 +grożąc|gro=żąc|5 +grubachny|gru=bach=ny|5 +gruntu|grun=tu|5 +grzbiety|grzbie=ty|5 +gwarem|gwa=rem|5 +gwarów|gwa=rów|5 +gąszczów|gąsz=czów|5 +gębusię|gę=bu=się|5 +gęgoty|gę=go=ty|5 +gęstwy|gę=stwy|5 +gęstych|gę=stych|5 +głodne|głod=ne|5 +głowach|gło=wach|5 +głowiny|gło=wi=ny|5 +głośne|gło=śne|5 +głupiemu|głu=pie=mu|5 +głębin|głę=bin|5 +hukiem|hu=kiem|5 +hukliwie|hu=kli=wie|5 +huknęli|huk=nę=li|5 +huknęły|huk=nę=ły|5 +hurkotał|hur=ko=tał|5 +idących|idą=cych|5 +inszej|in=szej|5 +Jagustynce|Ja=gu=styn=ce|5 +Jagustynkę|Ja=gu=styn=kę|5 +Jakieś|Ja=kieś|5 +jarzące|ja=rzą=ce|5 +jarzących|ja=rzą=cych|5 +jastrzębie|ja=strzę=bie|5 +jazgotliwe|ja=zgo=tli=we|5 +jałowców|ja=łow=ców|5 +jechała|je=cha=ła|5 +jednemu|jed=ne=mu|5 +jedyna|je=dy=na|5 +jedyny|je=dy=ny|5 +jensze|jen=sze|5 +jesienne|je=sien=ne|5 +jesiennych|je=sien=nych|5 +Jesień|Je=sień|5 +Jezusa|Je=zu=sa|5 +jęczały|ję=cza=ły|5 +jękliwy|ję=kli=wy|5 +kalkulował|kal=ku=lo=wał|5 +kamienia|ka=mie=nia|5 +kamionek|ka=mio=nek|5 +kapelusz|ka=pe=lusz|5 +kazali|ka=za=li|5 +kałduny|kał=du=ny|5 +Każdemu|Każ=de=mu|5 +kiełbas|kieł=bas|5 +kilkadziesiąt|kil=ka=dzie=siąt|5 +klepisku|kle=pi=sku|5 +klucze|klu=cze|5 +klątwy|klą=twy|5 +klęczał|klę=czał|5 +klęczek|klę=czek|5 +kobiecych|ko=bie=cych|5 +kolanach|ko=la=nach|5 +kolanami|ko=la=na=mi|5 +kolebać|ko=le=bać|5 +kolebał|ko=le=bał|5 +kolorowych|ko=lo=ro=wych|5 +kominów|ko=mi=nów|5 +komornicy|ko=mor=ni=cy|5 +komorniki|ko=mor=ni=ki|5 +koników|ko=ni=ków|5 +konisko|ko=ni=sko|5 +kopcach|kop=cach|5 +korzeniami|ko=rze=nia=mi|5 +korzeń|ko=rzeń|5 +kostki|kost=ki|5 +kołkami|koł=ka=mi|5 +kołować|ko=ło=wać|5 +kożucha|ko=żu=cha|5 +kreminał|kre=mi=nał|5 +kropidło|kro=pi=dło|5 +krowami|kro=wa=mi|5 +krwawy|krwa=wy|5 +krwawych|krwa=wych|5 +kryjąc|kry=jąc|5 +kryminale|kry=mi=na=le|5 +kryminał|kry=mi=nał|5 +krzach|krzach|5 +krzaki|krza=ki|5 +krzykać|krzy=kać|5 +krzątając|krzą=ta=jąc|5 +krążyły|krą=ży=ły|5 +kręciła|krę=ci=ła|5 +kręciło|krę=ci=ło|5 +kręgiem|krę=giem|5 +którejś|któ=rejś|5 +Kubowa|Ku=bo=wa|5 +kujnął|kuj=nął|5 +kulasa|ku=la=sa|5 +kużdego|kuż=de=go|5 +kwardo|kwar=do|5 +kwietne|kwiet=ne|5 +kępami|kę=pa=mi|5 +kłoniły|kło=ni=ły|5 +kłócili|kłó=ci=li|5 +Kłębem|Kłę=bem|5 +lakudra|la=ku=dra|5 +lampki|lamp=ki|5 +latach|la=tach|5 +latego|la=te=go|5 +lepsza|lep=sza|5 +lepszych|lep=szych|5 +licząca|li=czą=ca|5 +Lipcom|Lip=com|5 +Lipczaków|Lip=cza=ków|5 +litery|li=te=ry|5 +litości|li=to=ści|5 +loboga|lo=bo=ga|5 +lękiem|lę=kiem|5 +machorki|ma=chor=ki|5 +Maciuś|Ma=ciuś|5 +macochą|ma=co=chą|5 +Magdzie|Mag=dzie|5 +majstrował|maj=stro=wał|5 +makiem|ma=kiem|5 +mamrotała|mam=ro=ta=ła|5 +marnacji|mar=na=cji|5 +Marysi|Ma=ry=si|5 +marzenia|ma=rze=nia|5 +marzeń|ma=rzeń|5 +masłem|ma=słem|5 +matczynej|mat=czy=nej|5 +matuli|ma=tu=li|5 +medytować|me=dy=to=wać|5 +miarkuje|miar=ku=je|5 +miasteczka|mia=stecz=ka|5 +miasto|mia=sto|5 +Michała|Mi=cha=ła|5 +mierzi|mier=zi|5 +mieszkania|miesz=ka=nia|5 +mieszkał|miesz=kał|5 +mignęła|mi=gnę=ła|5 +miłowanie|mi=ło=wa=nie|5 +miłował|mi=ło=wał|5 +miłościwy|mi=ło=ści=wy|5 +mocnym|moc=nym|5 +modrawych|mo=dra=wych|5 +modrawą|mo=dra=wą|5 +modrych|mo=drych|5 +modrzały|mo=drza=ły|5 +mogłaby|mo=gła=by|5 +moiściewy|mo=iście=wy|5 +motyczkami|mo=tycz=ka=mi|5 +możemy|mo=że=my|5 +mrocznej|mrocz=nej|5 +mroczno|mrocz=no|5 +mrokiem|mro=kiem|5 +muzykanci|mu=zy=kan=ci|5 +muzyką|mu=zy=ką|5 +mąciło|mą=ci=ło|5 +młodym|mło=dym|5 +młotem|mło=tem|5 +młynarczyka|mły=nar=czy=ka|5 +nabożnym|na=boż=nym|5 +nabrał|na=brał|5 +naczynia|na=czy=nia|5 +nadziei|na=dziei|5 +naglądać|na=glą=dać|5 +nagrzany|na=grza=ny|5 +nagłym|na=głym|5 +najczęściej|naj=czę=ściej|5 +najmilejsza|naj=mi=lej=sza|5 +najmniej|naj=mniej|5 +najpierwej|naj=pier=wej|5 +najspokojniej|naj=spo=koj=niej|5 +największe|naj=więk=sze|5 +największą|naj=więk=szą|5 +namyśle|na=my=śle|5 +naprędce|na=pręd=ce|5 +naradę|na=ra=dę|5 +narobiła|na=ro=bi=ła|5 +Narodzie|Na=ro=dzie|5 +narządzić|na=rzą=dzić|5 +nastawał|na=sta=wał|5 +naszczekiwania|na=szcze=ki=wa=nia|5 +natknąwszy|na=tknąw=szy|5 +nawrócił|na=wró=cił|5 +niebieską|nie=bie=ską|5 +nieboszczyk|nie=bosz=czyk|5 +niedaleczko|nie=da=lecz=ko|5 +Niedługo|Nie=dłu=go|5 +niegdzie|nie=gdzie|5 +niejedni|nie=jed=ni|5 +nienawiści|nie=na=wi=ści|5 +nienawiść|nie=na=wiść|5 +nieodstępnie|nie=od=stęp=nie|5 +niepamięci|nie=pa=mię=ci|5 +niepomiernie|nie=po=mier=nie|5 +nieprzejrzane|nie=przej=rza=ne|5 +nieprzerwanym|nie=prze=rwa=nym|5 +nieprzytomnym|nie=przy=tom=nym|5 +nieskończone|nie=skoń=czo=ne|5 +nieskończonym|nie=skoń=czo=nym|5 +niesporo|nie=spo=ro|5 +nieszczęsna|nie=szczę=sna|5 +nieszczęścia|nie=szczę=ścia|5 +nieszporach|nie=szpo=rach|5 +nieubłaganą|nie=ubła=ga=ną|5 +nieustępliwie|nie=ustę=pli=wie|5 +nieustępliwy|nie=ustę=pli=wy|5 +niewola|nie=wo=la|5 +niezgorsza|nie=zgor=sza|5 +niezgłębioną|nie=zgłę=bio=ną|5 +niezmierną|nie=zmier=ną|5 +nikiej|ni=kiej|5 +nowinki|no=win=ki|5 +Nowoczesna|No=wo=cze=sna|5 +Obaczcie|Obacz=cie|5 +Obaczycie|Oba=czy=cie|5 +Obejrzała|Obej=rza=ła|5 +obrazik|ob=ra=zik|5 +obrazu|ob=ra=zu|5 +obroku|ob=ro=ku|5 +obrosłe|ob=ro=słe|5 +obszedł|ob=szedł|5 +obwisłe|ob=wi=słe|5 +obwisłymi|ob=wi=sły=mi|5 +obłożony|ob=ło=żo=ny|5 +ocierając|ocie=ra=jąc|5 +oczymgnieniu|oczym=gnie=niu|5 +odbierze|od=bie=rze|5 +oddawał|od=da=wał|5 +odejściu|odej=ściu|5 +oderwał|ode=rwał|5 +odgłosy|od=gło=sy|5 +odjechał|od=je=chał|5 +odkrzyknęła|od=krzyk=nę=ła|5 +odmawiać|od=ma=wiać|5 +odmawiał|od=ma=wiał|5 +odprawiała|od=pra=wia=ła|5 +odwracały|od=wra=ca=ły|5 +odzywała|od=zy=wa=ła|5 +odzywały|od=zy=wa=ły|5 +odświętnie|od=święt=nie|5 +oganiać|oga=niać|5 +ogląda|oglą=da|5 +ogniami|ognia=mi|5 +ognisty|ogni=sty|5 +ogrodzenie|ogro=dze=nie|5 +ogromna|ogrom=na|5 +ojcowa|oj=co=wa|5 +ojcowy|oj=co=wy|5 +okolicach|oko=li=cach|5 +okresach|okre=sach|5 +okrwawiony|okrwa=wio=ny|5 +oktawę|okta=wę|5 +okwiat|okwiat|5 +omijali|omi=ja=li|5 +opamiętała|opa=mię=ta=ła|5 +oparcie|opar=cie|5 +opowiadań|opo=wia=dań|5 +oprócz|oprócz|5 +orzydla|orzy=dla|5 +ostaje|osta=je|5 +ostaniesz|osta=niesz|5 +ostatek|osta=tek|5 +ostatniego|ostat=nie=go|5 +ostrym|ostrym|5 +oszroniałych|oszro=nia=łych|5 +otwartych|otwar=tych|5 +otwierając|otwie=ra=jąc|5 +otworzyła|otwo=rzy=ła|5 +ozewrzeć|ozew=rzeć|5 +ozimin|ozi=min|5 +oziminy|ozi=mi=ny|5 +oślepłe|ośle=płe|5 +paczkę|pacz=kę|5 +padając|pa=da=jąc|5 +palmami|pal=ma=mi|5 +pamiętajcie|pa=mię=taj=cie|5 +pamiętam|pa=mię=tam|5 +Panienka|Pa=nien=ka|5 +papierem|pa=pie=rem|5 +parafia|pa=ra=fia|5 +parszywego|par=szy=we=go|5 +parzył|pa=rzył|5 +parzyła|pa=rzy=ła|5 +pasiastych|pa=sia=stych|5 +paskudny|pa=skud=ny|5 +pastwiska|pa=stwi=ska|5 +patrzeli|pa=trze=li|5 +patrzyli|pa=trzy=li|5 +pewnością|pew=no=ścią|5 +pełzały|peł=za=ły|5 +pierun|pie=run|5 +pierzyna|pie=rzy=na|5 +piesku|pie=sku|5 +Pietras|Pie=tras|5 +pijani|pi=ja=ni|5 +pijanica|pi=ja=ni=ca|5 +pijcie|pij=cie|5 +pilnujcie|pil=nuj=cie|5 +pisane|pi=sa=ne|5 +pisarzem|pi=sa=rzem|5 +piskorz|pi=skorz|5 +piszczy|pisz=czy|5 +pięciu|pię=ciu|5 +piękna|pięk=na|5 +Piękny|Pięk=ny|5 +plątała|plą=ta=ła|5 +plątały|plą=ta=ły|5 +pobladły|po=bla=dły|5 +pocałowała|po=ca=ło=wa=ła|5 +pochylając|po=chy=la=jąc|5 +pociągnęli|po=cią=gnę=li|5 +poczciwa|po=czci=wa|5 +poczeka|po=cze=ka|5 +poczerwieniał|po=czer=wie=niał|5 +poczynało|po=czy=na=ło|5 +poczynały|po=czy=na=ły|5 +podatek|po=da=tek|5 +podawał|po=da=wał|5 +podjadł|pod=jadł|5 +Podniesienie|Pod=nie=sie=nie|5 +podnosili|pod=no=si=li|5 +podobna|po=dob=na|5 +podorędziu|po=do=rę=dziu|5 +podrapał|po=dra=pał|5 +podtykała|pod=ty=ka=ła|5 +podzieje|po=dzie=je|5 +podzieję|po=dzie=ję|5 +podzięce|po=dzię=ce|5 +pogadywała|po=ga=dy=wa=ła|5 +pogadywały|po=ga=dy=wa=ły|5 +pogardliwie|po=gar=dli=wie|5 +Pogoda|Po=go=da|5 +pogroza|po=gro=za|5 +pogrozy|po=gro=zy|5 +pogwizdywał|po=gwiz=dy=wał|5 +pogłaskał|po=gła=skał|5 +pojadał|po=ja=dał|5 +Pokażcie|Po=każ=cie|5 +pokosy|po=ko=sy|5 +pokrako|po=kra=ko|5 +pokryte|po=kry=te|5 +pokrywał|po=kry=wał|5 +pokrzykiwał|po=krzy=ki=wał|5 +pokręcił|po=krę=cił|5 +pokutę|po=ku=tę|5 +pokładali|po=kła=da=li|5 +polazł|po=lazł|5 +poleci|po=le=ci|5 +pomagając|po=ma=ga=jąc|5 +pomagało|po=ma=ga=ło|5 +pomarli|po=mar=li|5 +pomarła|po=mar=ła|5 +pomarłych|po=mar=łych|5 +pomiarkowali|po=miar=ko=wa=li|5 +pomiarkuj|po=miar=kuj|5 +pomogły|po=mo=gły|5 +pomstować|po=msto=wać|5 +pomstowała|po=msto=wa=ła|5 +poniechaj|po=nie=chaj|5 +poniechali|po=nie=cha=li|5 +popołudnie|po=po=łu=dnie|5 +poprowadzi|po=pro=wa=dzi|5 +popuścić|po=pu=ścić|5 +portek|por=tek|5 +porwali|po=rwa=li|5 +Porwał|Po=rwał|5 +porywając|po=ry=wa=jąc|5 +porębie|po=rę=bie|5 +postawi|po=sta=wi|5 +postawią|po=sta=wią|5 +postawiła|po=sta=wi=ła|5 +postąpił|po=stą=pił|5 +posypał|po=sy=pał|5 +Poszłabym|Po=szła=bym|5 +posępne|po=sęp=ne|5 +posępnie|po=sęp=nie|5 +posłać|po=słać|5 +posłuchu|po=słu=chu|5 +potrafiła|po=tra=fi=ła|5 +potrzebie|po=trze=bie|5 +potężne|po=tęż=ne|5 +Powiadają|Po=wia=da=ją|5 +Powiadam|Po=wia=dam|5 +Powiadał|Po=wia=dał|5 +Powiedają|Po=wie=da=ją|5 +powiedając|po=wie=da=jąc|5 +Powiedali|Po=wie=da=li|5 +powiedzcie|po=wiedz=cie|5 +powiedzenie|po=wie=dze=nie|5 +Powiedziałem|Po=wie=dzia=łem|5 +powiekami|po=wie=ka=mi|5 +powiesić|po=wie=sić|5 +powiew|po=wiew|5 +powinno|po=win=no|5 +powinny|po=win=ny|5 +powlekła|po=wle=kła|5 +powrósła|po=wró=sła|5 +powstały|po=wsta=ły|5 +pozdrawiał|po=zdra=wiał|5 +poznali|po=zna=li|5 +pozostać|po=zo=stać|5 +pozwalał|po=zwa=lał|5 +pozwolił|po=zwo=lił|5 +pozwolę|po=zwo=lę|5 +połcie|po=łcie|5 +połowę|po=ło=wę|5 +Południe|Po=łu=dnie|5 +pożaru|po=ża=ru|5 +pożywi|po=ży=wi|5 +pradziada|pra=dzia=da|5 +prażyło|pra=ży=ło|5 +proboszczem|pro=bosz=czem|5 +progach|pro=gach|5 +prosiaka|pro=sia=ka|5 +prosięta|pro=się=ta|5 +prosiętami|pro=się=ta=mi|5 +proste|pro=ste|5 +Prosto|Pro=sto|5 +prostowała|pro=sto=wa=ła|5 +prosty|pro=sty|5 +proszalne|pro=szal=ne|5 +proszonym|pro=szo=nym|5 +przebierały|prze=bie=ra=ły|5 +przebrał|prze=brał|5 +przechylił|prze=chy=lił|5 +przecie|prze=cie|5 +przeciągały|prze=cią=ga=ły|5 +przeciągnął|prze=cią=gnął|5 +przeczytał|prze=czy=tał|5 +przednim|przed=nim|5 +przegryźć|prze=gryźć|5 +przejmowały|przej=mo=wa=ły|5 +przejrzyste|przej=rzy=ste|5 +przekładał|prze=kła=dał|5 +przelewa|prze=le=wa|5 +przemawiać|prze=ma=wiać|5 +przemienił|prze=mie=nił|5 +przemogła|prze=mo=gła|5 +przemoże|prze=mo=że|5 +przemyślnie|prze=myśl=nie|5 +przeniesła|prze=nie=sła|5 +przenikliwy|prze=ni=kli=wy|5 +przenosiny|prze=no=si=ny|5 +Przepijcie|Prze=pij=cie|5 +przepiórki|prze=piór=ki|5 +przepomniał|prze=po=mniał|5 +przepomniała|prze=po=mnia=ła|5 +przepraszać|prze=pra=szać|5 +przeraźliwie|prze=raź=li=wie|5 +przerywając|prze=ry=wa=jąc|5 +przeschnie|prze=schnie|5 +przesiadywał|prze=sia=dy=wał|5 +przesiedział|prze=sie=dział|5 +przestała|prze=sta=ła|5 +przetak|prze=tak|5 +Przewrotny|Prze=wrot=ny|5 +przezwiska|prze=zwi=ska|5 +przezwisko|prze=zwi=sko|5 +przełknąć|prze=łknąć|5 +prześmiewał|prze=śmie=wał|5 diff --git a/test/run_differential_rounding_test.sh b/test/run_differential_rounding_test.sh index dd7aa5c7..e73aaf0e 100755 --- a/test/run_differential_rounding_test.sh +++ b/test/run_differential_rounding_test.sh @@ -10,6 +10,7 @@ mkdir -p "$BUILD_DIR" SOURCES=( "$ROOT_DIR/test/differential_rounding/DifferentialRoundingTest.cpp" "$ROOT_DIR/lib/EpdFont/EpdFont.cpp" + "$ROOT_DIR/lib/EpdFont/EpdFontFamily.cpp" "$ROOT_DIR/lib/Utf8/Utf8.cpp" )