pixel_font_trie_ocr 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3ef0ba96a8d1f2d871122d57bff74f315c07e75367ee1d823462ae534c7018ee
4
+ data.tar.gz: a146081f4bc5feb0b930eec43287a2dd7ded02ee62d52c5dac2f7242753a6b08
5
+ SHA512:
6
+ metadata.gz: 7b76df9489ccf4e0ce2a89e10a574d08fc37e76f8f07f820553439f9467c3086a456298f66ec2cc7b6ab5169ddd7cf746549a57242e54e3eab493ecc68a2ea6d
7
+ data.tar.gz: 443df30b953382a1cef4af8abf3d4e592b30583bc7b852d9a307c7dcf741c57dfbc98908de9e20541c02b9e067bb156f3675e340db1b2a9b32a67ce167f4cae8
data/AGENTS.md ADDED
@@ -0,0 +1,72 @@
1
+ # AGENTS.md – PixelFontTrieOCR
2
+
3
+ **IMPORTANT: Think and Communicate with the user ONLY in American English. Do NOT use any oriental glyphs or non-English characters.**
4
+ You are pair programming with the user.
5
+ If you make changes, the user will review.
6
+ If the user makes changes, they will ask you to review.
7
+
8
+ ## Project Overview
9
+ Deterministic OCR for tiny (5–8px) crystal-clear pixel fonts using a multi-way Trie keyed by 8-bit column bitmasks (black=1, white=0). Replaces noisy general OCR (Tesseract) with perfect accuracy, early pruning, and microsecond performance on exact fonts.
10
+
11
+ **Gem name:** pixel_font_trie_ocr
12
+ **Module:** `PixelFontTrieOCR`
13
+ **Core tech:** RMagick for image handling, RSpec + RuboCop for testing/linting.
14
+
15
+ ## Code Style & Conventions
16
+ - **No inline comments after code** in source files. Comments before methods, classes or modules following rdoc conventions.
17
+ - Heavy memoization: `@var ||= ...` for `img`, `width`, `image`, `draw`, etc.
18
+ - Short, single-responsibility methods with public accessors (`width`, `image`, `extract`).
19
+ - Seperation of concerns, is important. Don't many any one method, module or class do too much.. Break it down.
20
+ - Mimic existing patterns from neighboring files (see `lib/pixel_font_trie_ocr/*.rb`).
21
+ - Use in-memory `Magick::Image` objects wherever possible (avoid temp files in tests).
22
+ - Follow RubyGem structure; update gemspec on name/module changes.
23
+ - **NEVER** introduce secrets, assume libraries only if already in Gemfile/gemspec.
24
+ - If a gem library would be useful for a task, ask the user about adding the gem.
25
+
26
+ ## Important Commands
27
+ - Full test + lint: `bundle exec rake` (default task: spec + rubocop)
28
+ - Run specs only: `bundle exec rspec`
29
+ - Console: `bin/console`
30
+ - Build gem: `bundle exec rake build`
31
+
32
+ **Always run `bundle exec rake` after changes. If there are errors, go into plan mode, and ask the user before making changes.**
33
+ If the specs pass, then have the user review the changes before making a commit.
34
+ Make rubocop fixes either automatic or manual in a seperate commit.
35
+ **never make changes on the master branch, Ask the user to create a working branch before making changes**
36
+ use descriptive commit messages to describe the changes made.
37
+ If there are changes that you don't recognize, they may be from the user, ask if they should be included.
38
+ Preferentially write specs before writing code, use a red green refactor process. red for code that fails the spec, green for getting the code to pass the spec, refactor for improving the code after it passes.
39
+
40
+ ## Key Components
41
+ - `ImageColumnExtractor`: Path or `Magick::Image` → array of column masks (8-bit ints). Supports `height_limit`, `threshold`.
42
+ - `ColumnImageBuilder`: Masks + height → test image (`#image`, `#write`).
43
+ - `TextImageBuilder`: Text + TTF font → clean pixel image (`#image`, `#write`, memoized metrics).
44
+ - `PixelFontTrie`:
45
+ - `insert(columns, char)`
46
+ - `recognize(columns)` (handles whitespace columns, variable-width glyphs)
47
+ - `PixelFontTrie.from_font(font_path, characters: "...", font_size: 8)` (builds from TrueType, trims whitespace).
48
+
49
+ ## Testing Approach
50
+ - RSpec in `spec/`.
51
+ - Use `ColumnImageBuilder` to generate fixtures (e.g. exhaustive 256-pattern column test).
52
+ - Prefer in-memory tests over real image files.
53
+ - spec files should match the source files with the change from /lib to /spec and the addition of _spec for the spec files.
54
+
55
+ ## Usage
56
+ ```ruby
57
+ trie = PixelFontTrieOCR::PixelFontTrie.from_font("font.ttf", characters: "ABC123!?")
58
+ masks = PixelFontTrieOCR::ImageColumnExtractor.new(image).extract
59
+ puts trie.recognize(masks)
60
+ ```
61
+
62
+ ## Rake Tasks
63
+ - `bundle exec rake generate_glyphs` – Generate glyph images from font (default: `fonts/hex-synergy_font.ttf` → `tmp/glyphs/`).
64
+ - Env vars: `FONT_PATH`, `OUTPUT_DIR`, `FONT_SIZE`, `HEIGHT`.
65
+ - Uses `PixelFontTrieOCR::GlyphImageGenerator`.
66
+
67
+ ## Next Steps (from project recap)
68
+ - Full recognition pipeline on real samples.
69
+ - CLI tool (`bin/pixel-font-trie-ocr`).
70
+ - Polish, release as gem.
71
+
72
+ Update this file when adding new commands, conventions, or architecture changes.
data/CHANGELOG.md ADDED
@@ -0,0 +1,6 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (Initial Release)
4
+ - Initial implementation of Trie-based OCR for pixel fonts.
5
+ - Support for font metadata extraction, image rendering, bitmask generation, and text recognition.
6
+ - Basic tests and RuboCop integration.
@@ -0,0 +1,10 @@
1
+ # Code of Conduct
2
+
3
+ "pixel_font_trie_ocr" follows [The Ruby Community Conduct Guideline](https://www.ruby-lang.org/en/conduct) in all "collaborative space", which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.):
4
+
5
+ * Participants will be tolerant of opposing views.
6
+ * Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
7
+ * When interpreting the words and actions of others, participants should always assume good intentions.
8
+ * Behaviour which can be reasonably considered harassment will not be tolerated.
9
+
10
+ If you have any concerns about behaviour within this project, please contact us at ["rob@ferney.org"](mailto:"rob@ferney.org").
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Robert Ferney
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,151 @@
1
+ # PixelFontTrieOCR
2
+
3
+ **Deterministic OCR for tiny (5–8px) crystal-clear pixel fonts using a multi-way Trie keyed by 8-bit column bitmasks.**
4
+
5
+ Replaces noisy general OCR (e.g., Tesseract) with perfect accuracy, early pruning, and microsecond performance on exact fonts.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'pixel_font_trie_ocr'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ ```bash
18
+ $ bundle install
19
+ ```
20
+
21
+ Or install it yourself as:
22
+
23
+ ```bash
24
+ $ gem install pixel_font_trie_ocr
25
+ ```
26
+
27
+ **Dependencies**: Requires RMagick for image handling and TTFunk for font parsing.
28
+
29
+ ## Quick Start
30
+
31
+ ```ruby
32
+ require 'pixel_font_trie_ocr'
33
+
34
+ ocr = PixelFontTrieOCR.new
35
+ ocr.font_name = 'hex-synergy_font.ttf' # Default font
36
+ ocr.font_size = 8 # Default size
37
+
38
+ # Render text to image
39
+ img = ocr.text_image('Hello World')
40
+
41
+ # Extract column bitmasks
42
+ masks = ocr.bitmask(img)
43
+
44
+ # Recognize text
45
+ text = ocr.parse_mask(masks)
46
+ puts text # => "Hello World"
47
+
48
+ # Or directly parse an image
49
+ text = ocr.parse_image(img)
50
+ ```
51
+
52
+ ## Advanced Usage
53
+
54
+ ### Building and Using the Trie
55
+
56
+ ```ruby
57
+ ocr = PixelFontTrieOCR.new
58
+
59
+ # Generate character masks from font (memoized)
60
+ char_masks = ocr.char_masks # { 'A' => [mask1, mask2, ...], ... }
61
+
62
+ # Build trie
63
+ trie = ocr.trie # Or PixelFontTrieOCR::Trie.new(char_masks)
64
+
65
+ # Insert custom character
66
+ trie.insert('!', [0b101, 0b010])
67
+
68
+ # Parse masks
69
+ masks = [0b101, 0b010, 0] # Example masks with trailing zero
70
+ text = trie.parse(masks) # Handles variable-width and whitespace
71
+ ```
72
+
73
+ ### Image Manipulation
74
+
75
+ ```ruby
76
+ # Create image from masks
77
+ mask_img = ocr.mask_image(masks)
78
+
79
+ # Write text image to file
80
+ ocr.write_text_image('Test', 'test.png')
81
+
82
+ # Bitmask utilities
83
+ bitmask = ocr.array_to_bitmask([1, 0, 1]) # => 5
84
+ bits = ocr.bitmask_to_array(5) # => [1, 0, 1]
85
+ ```
86
+
87
+ ### Custom Font Configuration
88
+
89
+ ```ruby
90
+ ocr.font_dir = '/path/to/fonts'
91
+ ocr.font_name = 'custom_font.ttf'
92
+ ocr.font_size = 6
93
+
94
+ puts ocr.height # Calculated pixel height
95
+ puts ocr.characters.to_a # Supported characters
96
+ ```
97
+
98
+ ### Extracting Masks from Existing Image
99
+
100
+ ```ruby
101
+ img = Magick::Image.read('path/to/image.png').first
102
+ extractor = PixelFontTrieOCR::ImageColumnExtractor.new(img, threshold: 50000)
103
+ masks = extractor.extract
104
+ ```
105
+
106
+ ## Features
107
+
108
+ - **Deterministic Recognition**: 100% accuracy for exact pixel matches; no ML heuristics.
109
+ - **Fast**: Microsecond per character; early trie pruning.
110
+ - **Variable-Width Support**: Handles fonts with varying glyph widths.
111
+ - **Memoization**: Heavy use for performance (e.g., font loading, mask generation).
112
+ - **Utilities**: Image rendering, bitmask conversion, font metadata extraction.
113
+ - **Customization**: Adjustable threshold for black/white detection in images.
114
+
115
+ ## Core Components
116
+
117
+ | Component | Purpose |
118
+ |----------------------------|---------|
119
+ | `PixelFontTrieOCR` | Main class including all modules; entry point for usage. |
120
+ | `Trie` | Core trie for inserting masks and parsing text. |
121
+ | `Trie::Node` | Trie nodes with recursive matching. |
122
+ | `ImageColumnExtractor` | Converts images to bitmask arrays. |
123
+ | `FontMetadata` (module) | Font loading and metadata (characters, ascent, etc.). |
124
+ | `ImageUtils` (module) | Image creation and writing (text_image, mask_image). |
125
+ | `Parsing` (module) | Builds trie and performs recognition (parse_image). |
126
+ | `Methods` (module) | Bitmask utilities (array_to_bitmask). |
127
+
128
+ ## Development
129
+
130
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rspec` to run the tests.
131
+
132
+ You can also run `bin/console` for an interactive prompt that will allow you to experiment.
133
+
134
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
135
+
136
+ ### Rake Tasks
137
+
138
+ - `rake spec`: Run RSpec tests.
139
+ - `rake rubocop`: Run RuboCop linter.
140
+ - `rake font_map`: Generate glyph images and YAML mask map to `/tmp/`.
141
+ - `rake build`: Build the gem.
142
+ - `rake install`: Install locally.
143
+ - `rake release`: Tag and publish to RubyGems.
144
+
145
+ ## Contributing
146
+
147
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[YOUR_USERNAME]/pixel_font_trie_ocr. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[YOUR_USERNAME]/pixel_font_trie_ocr/blob/master/CODE_OF_CONDUCT.md).
148
+
149
+ ## License
150
+
151
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+ require "rubocop/rake_task"
6
+ require "fileutils"
7
+ require_relative "lib/pixel_font_trie_ocr"
8
+
9
+ RSpec::Core::RakeTask.new(:spec)
10
+ RuboCop::RakeTask.new
11
+ Rake.add_rakelib(File.join(__dir__, "lib", "tasks"))
12
+
13
+ task default: %i[spec rubocop]
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ttfunk"
4
+
5
+ class PixelFontTrieOCR
6
+ module FontMetadata
7
+ DEFAULT_FONT_NAME = "hex-synergy_font.ttf"
8
+ attr_writer :font_name, :font_size
9
+
10
+ def font_name
11
+ @font_name ||= DEFAULT_FONT_NAME
12
+ end
13
+
14
+ def font_dir=(value)
15
+ @font_dir = Pathname.new(value)
16
+ end
17
+
18
+ def font_dir
19
+ @font_dir ||= Pathname.new(__dir__).join("fonts")
20
+ end
21
+
22
+ def font_path
23
+ @font_path ||= font_dir.join(font_name).to_s
24
+ end
25
+
26
+ def font_size
27
+ @font_size ||= 8
28
+ end
29
+
30
+ def font
31
+ @font ||= TTFunk::File.open(font_path)
32
+ end
33
+
34
+ def font_map
35
+ @font_map ||= font.cmap.unicode.first
36
+ end
37
+
38
+ def code_map
39
+ @code_map ||= font_map.respond_to?(:code_map) ? font_map.code_map : {}
40
+ end
41
+
42
+ def characters
43
+ @characters ||= Set.new(
44
+ code_map.filter_map do |key, value|
45
+ value.positive? && key > 31 && [key].pack("U")
46
+ end
47
+ )
48
+ end
49
+
50
+ def family
51
+ @family ||= font.name.font_name
52
+ end
53
+
54
+ def subfamily
55
+ @subfamily ||= font.name.font_subfamily
56
+ end
57
+
58
+ def postscript_name
59
+ @postscript_name ||= font.name.postscript_name
60
+ end
61
+
62
+ def character_count
63
+ @character_count ||= characters.size
64
+ end
65
+
66
+ def ascent
67
+ @ascent ||= font.os2.ascent
68
+ end
69
+
70
+ def descent
71
+ @descent ||= font.os2.descent
72
+ end
73
+
74
+ def units_per_em
75
+ @units_per_em ||= font.header.units_per_em
76
+ end
77
+
78
+ def ascent_ratio
79
+ @ascent_ratio ||= ascent / units_per_em.to_f
80
+ end
81
+
82
+ def height
83
+ @height ||= (ascent_ratio * font_size).ceil
84
+ end
85
+
86
+ def uppercase
87
+ @uppercase ||= Set.new("A".."Z")
88
+ end
89
+
90
+ def lowercase
91
+ @lowercase ||= Set.new("a".."z")
92
+ end
93
+
94
+ def digits
95
+ @digits ||= Set.new("0".."9")
96
+ end
97
+
98
+ def alphanumeric
99
+ @alphanumeric ||= uppercase | lowercase | digits
100
+ end
101
+
102
+ def whitespace
103
+ Set.new([" ", "\r", "\n", "\t"])
104
+ end
105
+
106
+ def symbols
107
+ @symbols ||= characters - alphanumeric - whitespace
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PixelFontTrieOCR
4
+ class ImageColumnExtractor
5
+ attr_reader :image, :threshold
6
+
7
+ def initialize(image, threshold: 38_000)
8
+ @image = image
9
+ @threshold = threshold
10
+ end
11
+
12
+ def extract
13
+ trim_masks(masks)
14
+ end
15
+
16
+ private
17
+
18
+ def width
19
+ @width ||= image.columns
20
+ end
21
+
22
+ def height
23
+ @height ||= image.rows
24
+ end
25
+
26
+ def get_column(col)
27
+ pixels = image.get_pixels(col, 0, 1, height).reverse
28
+ pixels.map.with_index do |p, row|
29
+ black?(p) ? (1 << row) : 0
30
+ end.sum
31
+ end
32
+
33
+ def black?(pixel)
34
+ (pixel.red + pixel.green + pixel.blue) < threshold
35
+ end
36
+
37
+ def masks
38
+ (0...width).map { |col| get_column(col) }
39
+ end
40
+
41
+ def trim_masks(masks)
42
+ first_non_zero = masks.find_index(&:positive?)
43
+ return [0, 0] unless first_non_zero
44
+
45
+ last_non_zero = masks.rindex(&:positive?)
46
+ masks[first_non_zero..last_non_zero] + [0]
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PixelFontTrieOCR
4
+ module ImageUtils
5
+ def temp_dir=(value)
6
+ @temp_dir = Pathname.new(value)
7
+ end
8
+
9
+ def temp_dir
10
+ @temp_dir ||= Pathname.new(__dir__).join("..", "..", "tmp")
11
+ end
12
+
13
+ def temp_file(name)
14
+ temp_dir.join(name).to_s
15
+ end
16
+
17
+ def new_image(width)
18
+ Magick::Image.new(width, height) do |img|
19
+ img.background_color = "white"
20
+ end
21
+ end
22
+
23
+ def new_draw
24
+ draw = Magick::Draw.new
25
+ draw.font = font_path
26
+ draw.pointsize = font_size
27
+ draw.fill = "black"
28
+ draw
29
+ end
30
+
31
+ def text_image(text, pad: 0)
32
+ draw = new_draw
33
+ metrics = draw.get_type_metrics(text)
34
+ width = metrics.width + pad
35
+ width = 2 if width.zero?
36
+ puts "drawing #{text.inspect} width: #{width}" if width < 1
37
+ image = new_image(width.ceil)
38
+ draw.text(0, metrics.ascent.ceil, text).draw(image)
39
+ image
40
+ end
41
+
42
+ def write_text_image(text, name, pad: 0)
43
+ image = text_image(text, pad: pad)
44
+ image.write(temp_file(name))
45
+ end
46
+
47
+ def bitmask(image)
48
+ ImageColumnExtractor.new(image).extract
49
+ end
50
+
51
+ def mask_image(mask_columns)
52
+ draw = new_draw
53
+ image = new_image(mask_columns.length)
54
+ mask_columns.each_with_index do |mask, col|
55
+ bitmask_to_array(mask).each_with_index do |bit, row|
56
+ draw.point(col, height - row - 1) if bit.positive?
57
+ end
58
+ end
59
+ draw.draw(image)
60
+ image
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PixelFontTrieOCR
4
+ module Methods
5
+ def array_to_bitmask(array)
6
+ array.inject(0) { |acc, bit| (acc << 1) | bit }
7
+ end
8
+
9
+ def bitmask_to_array(bitmask, length: nil)
10
+ Array.new(length || bitmask.bit_length) { |i| (bitmask >> i) & 1 }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "trie"
4
+
5
+ class PixelFontTrieOCR
6
+ module Parsing
7
+ def character_images
8
+ @char_masks = {}
9
+ characters.map.with_index do |char, index|
10
+ image = text_image(char)
11
+ mask = bitmask(image)
12
+ yield(char, image, mask, index) if block_given?
13
+ @char_masks[char] = mask
14
+ end
15
+ @char_masks
16
+ end
17
+
18
+ def char_masks
19
+ @char_masks || character_images
20
+ end
21
+
22
+ def trie
23
+ @trie ||= Trie.new(char_masks)
24
+ end
25
+
26
+ def parse_mask(columns)
27
+ trie.parse(columns)
28
+ end
29
+
30
+ def parse_image(img)
31
+ parse_mask(bitmask(img))
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PixelFontTrieOCR
4
+ class Trie
5
+ class Node
6
+ attr_accessor :children, :character
7
+
8
+ def initialize
9
+ @children = {}
10
+ @character = nil
11
+ end
12
+
13
+ def leaf?
14
+ children.empty?
15
+ end
16
+
17
+ def match(columns, index)
18
+ if index < columns.length
19
+ child_char, child_idx = children[columns[index]]&.match(columns, index + 1)
20
+ return [child_char, child_idx] if child_char
21
+ end
22
+
23
+ [character, index]
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ # lib/pixel_font_trie_ocr/trie.rb
4
+ require_relative "trie/node"
5
+
6
+ class PixelFontTrieOCR
7
+ class Trie
8
+ def initialize(char_masks = {})
9
+ insert_hash(char_masks)
10
+ end
11
+
12
+ def root
13
+ @root ||= Node.new
14
+ end
15
+
16
+ def insert_hash(hash)
17
+ hash.each_pair do |character, columns|
18
+ insert(character, columns)
19
+ end
20
+ end
21
+
22
+ def insert(character, columns)
23
+ node = root
24
+ columns.each do |mask|
25
+ node.children[mask] ||= Node.new
26
+ node = node.children[mask]
27
+ end
28
+ node.character ||= character
29
+ end
30
+
31
+ def parse(columns)
32
+ columns = columns.dup
33
+ columns << 0 unless columns.last.zero?
34
+ match(columns)
35
+ end
36
+
37
+ protected
38
+
39
+ def match(columns, pos = 0)
40
+ return "" unless pos < columns.length
41
+
42
+ matched, new_pos = root.match(columns, pos)
43
+ if matched
44
+ matched + match(columns, new_pos)
45
+ else
46
+ match(columns, pos + 1)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PixelFontTrieOCR
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rmagick"
4
+ require "ttfunk"
5
+ require_relative "pixel_font_trie_ocr/version"
6
+ require_relative "pixel_font_trie_ocr/methods"
7
+ require_relative "pixel_font_trie_ocr/font_metadata"
8
+ require_relative "pixel_font_trie_ocr/image_utils"
9
+ require_relative "pixel_font_trie_ocr/parsing"
10
+ require_relative "pixel_font_trie_ocr/image_column_extractor"
11
+
12
+ class PixelFontTrieOCR
13
+ class Error < StandardError; end
14
+ include Methods
15
+ include FontMetadata
16
+ include ImageUtils
17
+ include Parsing
18
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ desc "Generate font glyph images and bitmask map to tmp/font/"
4
+ task :font_map do
5
+ require "yaml"
6
+ pft = PixelFontTrieOCR.new
7
+ pft.temp_dir.mkpath
8
+
9
+ map_data = pft.character_images
10
+ File.write("tmp/map.yaml", map_data.to_yaml)
11
+ puts "Generated #{map_data.size}"
12
+
13
+ pft.write_text_image(pft.uppercase.join, "uppercase.png")
14
+ pft.write_text_image(pft.lowercase.join, "lowercase.png")
15
+ pft.write_text_image(pft.digits.join, "digits.png")
16
+ pft.write_text_image(pft.symbols.join, "symbols.png", pad: 10)
17
+ end
@@ -0,0 +1,4 @@
1
+ class PixelFontTrieOCR
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pixel_font_trie_ocr
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Robert Ferney
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rmagick
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '6.3'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '6.3'
26
+ - !ruby/object:Gem::Dependency
27
+ name: ttfunk
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.8'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.8'
40
+ description: Provides perfect accuracy and microsecond performance for crystal-clear,
41
+ 5-8px pixel fonts by building a trie from font glyphs and matching image column
42
+ bitmasks.
43
+ email:
44
+ - rob@ferney.org
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - AGENTS.md
50
+ - CHANGELOG.md
51
+ - CODE_OF_CONDUCT.md
52
+ - LICENSE.txt
53
+ - README.md
54
+ - Rakefile
55
+ - lib/pixel_font_trie_ocr.rb
56
+ - lib/pixel_font_trie_ocr/font_metadata.rb
57
+ - lib/pixel_font_trie_ocr/fonts/hex-synergy_font.ttf
58
+ - lib/pixel_font_trie_ocr/image_column_extractor.rb
59
+ - lib/pixel_font_trie_ocr/image_utils.rb
60
+ - lib/pixel_font_trie_ocr/methods.rb
61
+ - lib/pixel_font_trie_ocr/parsing.rb
62
+ - lib/pixel_font_trie_ocr/trie.rb
63
+ - lib/pixel_font_trie_ocr/trie/node.rb
64
+ - lib/pixel_font_trie_ocr/version.rb
65
+ - lib/tasks/font_map.rake
66
+ - sig/pixel_font_trie_ocr.rbs
67
+ homepage: https://github.com/Barbary-Horde-Studios/pixel_font_trie_ocr
68
+ licenses:
69
+ - MIT
70
+ metadata:
71
+ allowed_push_host: https://rubygems.org
72
+ homepage_uri: https://github.com/Barbary-Horde-Studios/pixel_font_trie_ocr
73
+ source_code_uri: https://github.com/Barbary-Horde-Studios/pixel_font_trie_ocr
74
+ changelog_uri: https://github.com/Barbary-Horde-Studios/pixel_font_trie_ocr/blob/main/CHANGELOG.md
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 3.2.0
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubygems_version: 4.0.10
90
+ specification_version: 4
91
+ summary: Deterministic OCR for tiny pixel fonts using a Trie of column bitmasks
92
+ test_files: []