prawn-rtl-support 0.1.7 → 0.2.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 +4 -4
- data/CHANGELOG.md +67 -0
- data/CLAUDE.md +57 -0
- data/Gemfile +15 -0
- data/README.md +53 -9
- data/lib/prawn/rtl/bidi.rb +225 -0
- data/lib/prawn/rtl/connector/logic.rb +72 -14
- data/lib/prawn/rtl/connector.rb +47 -10
- data/lib/prawn/rtl/support/version.rb +1 -1
- data/lib/prawn/rtl/support.rb +19 -5
- data/prawn-rtl-support.gemspec +34 -24
- metadata +28 -112
- data/.gitignore +0 -14
- data/.rspec +0 -2
- data/.travis.yml +0 -7
- data/Rakefile +0 -6
- data/bin/console +0 -14
- data/bin/setup +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 233930e38a41afb8a807bf1753e171aa3a8112bacd425c57c8b93c2aecd374f2
|
4
|
+
data.tar.gz: b6ea130b2b05f7ea1f002f7023fbd22b5338324e7f0a4695175310c37d6ebb99
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 882f584327fb3d2cacddb945f46a37c07fc48f0c93e0b069fd3f567359f6df226b2a7ad1ed6f604907bc912228b2acfaf34a88f06adbcde58569d97c8428298e
|
7
|
+
data.tar.gz: 9602c40c2e4668188f1c8d91b79587a879a50207771b247f79ea7afd9721706a5310e21793e83453b275587b7b5dc5c4e70d2fbec43e8018e277b72a2d4f58cf
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
|
+
|
8
|
+
## [0.2.0] - 2025-09-04
|
9
|
+
|
10
|
+
### ⚠️ Breaking Changes
|
11
|
+
- **ICU library requirement**: The gem now requires the ICU library to be installed on the system. This may affect macOS and Windows users who previously relied on twitter_cldr's bundled ICU data.
|
12
|
+
- **Linux**: Usually pre-installed
|
13
|
+
- **macOS**: Install via Homebrew: `brew install icu4c`
|
14
|
+
- **Windows**: May require manual ICU installation
|
15
|
+
- **Custom path**: Set `ICU_LIB_PATH` environment variable if ICU is installed in a non-standard location
|
16
|
+
|
17
|
+
### Customer-Impacting Changes
|
18
|
+
|
19
|
+
#### Added
|
20
|
+
- **Extended platform support**: Added ARM64 architecture support in CI/CD pipeline
|
21
|
+
|
22
|
+
#### Changed
|
23
|
+
- **Dependencies**: Replaced twitter_cldr with direct FFI bindings (lighter dependency footprint, but requires system ICU library)
|
24
|
+
- **Performance**: Direct ICU integration provides better performance for text processing
|
25
|
+
|
26
|
+
### Internal Changes
|
27
|
+
|
28
|
+
#### Testing & Quality
|
29
|
+
- Added comprehensive integration tests for BiDi functionality
|
30
|
+
- Extended CI matrix to include Ubuntu 22.04, 24.04 and ARM64 variants
|
31
|
+
|
32
|
+
## [0.1.8] - 2025-09-04
|
33
|
+
|
34
|
+
### Customer-Impacting Changes
|
35
|
+
|
36
|
+
#### Added
|
37
|
+
- **Ruby 2.7+ requirement**: Gem now explicitly requires Ruby 2.7 or higher
|
38
|
+
- **Better documentation**: All public APIs now have YARD documentation with examples
|
39
|
+
|
40
|
+
### Internal Changes
|
41
|
+
|
42
|
+
#### Development & CI
|
43
|
+
- Migrated from Travis CI to GitHub Actions
|
44
|
+
- Added testing matrix for Ruby 2.7, 3.0, 3.1, 3.2, 3.3, and 3.4
|
45
|
+
- Added RuboCop linting to CI workflow
|
46
|
+
- Added Dependabot for automated dependency updates
|
47
|
+
- Added CLAUDE.md for AI-assisted development guidance
|
48
|
+
- Multi-factor authentication (MFA) is now required for gem publishers
|
49
|
+
|
50
|
+
#### Code Quality
|
51
|
+
- Added YARD documentation to all modules and classes
|
52
|
+
- Fixed some RuboCop violations
|
53
|
+
- Fixed typos
|
54
|
+
- Improved gemspec metadata (added source_code_uri, changelog_uri, bug_tracker_uri, documentation_uri)
|
55
|
+
- Moved development dependencies from gemspec to Gemfile
|
56
|
+
- Removed unnecessary `$LOAD_PATH` manipulation
|
57
|
+
- Internal file requires now use `require_relative` for faster loading
|
58
|
+
- Gemspec file inclusion no longer depends on git, works in any environment
|
59
|
+
|
60
|
+
#### Documentation
|
61
|
+
- Updated README with "Supported Languages" section
|
62
|
+
- Updated gemspec description to accurately reflect RTL language support
|
63
|
+
- Added comprehensive code examples in YARD documentation
|
64
|
+
|
65
|
+
## [0.1.7] - 2020-05-10
|
66
|
+
|
67
|
+
For changes in previous versions, see the git commit history.
|
data/CLAUDE.md
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# CLAUDE.md
|
2
|
+
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
4
|
+
|
5
|
+
## Project Overview
|
6
|
+
|
7
|
+
This is prawn-rtl-support, a Ruby gem that provides bidirectional text (RTL/LTR) support for the Prawn PDF generation library. It enables proper rendering of Arabic and other right-to-left languages in PDFs by:
|
8
|
+
- Using Unicode Bidirectional Algorithm via ICU for text reordering
|
9
|
+
- Connecting Arabic letters properly for visual display
|
10
|
+
- Minimally patching Prawn's text rendering pipeline
|
11
|
+
|
12
|
+
## Development Commands
|
13
|
+
|
14
|
+
```bash
|
15
|
+
# Install dependencies
|
16
|
+
bundle install
|
17
|
+
|
18
|
+
# Run test suite
|
19
|
+
bundle exec rake spec
|
20
|
+
# or just
|
21
|
+
rake spec
|
22
|
+
|
23
|
+
# Run linting (RuboCop)
|
24
|
+
bundle exec rubocop
|
25
|
+
|
26
|
+
# Open console for experimentation
|
27
|
+
bin/console
|
28
|
+
|
29
|
+
# Build gem locally
|
30
|
+
bundle exec rake build
|
31
|
+
|
32
|
+
# Install gem to local machine
|
33
|
+
bundle exec rake install
|
34
|
+
```
|
35
|
+
|
36
|
+
## Architecture
|
37
|
+
|
38
|
+
The gem patches Prawn by prepending a module to `Prawn::Text::Formatted::Box#original_text`. The core functionality:
|
39
|
+
|
40
|
+
1. **lib/prawn/rtl/support.rb**: Main entry point that patches Prawn::Text::Formatted::Box
|
41
|
+
2. **lib/prawn/rtl/connector.rb**: Core RTL fixing logic with three main methods:
|
42
|
+
- `fix_rtl(string)`: Main public API that detects RTL text and processes it
|
43
|
+
- `connect(string)`: Applies Arabic letter connection rules
|
44
|
+
- `reorder(string)`: Uses ICU's Bidi algorithm to reorder text visually
|
45
|
+
3. **lib/prawn/rtl/connector/logic.rb**: Arabic letter connection logic with character mapping tables for different forms (isolated, initial, medial, final)
|
46
|
+
|
47
|
+
The gem automatically activates when required - no configuration needed. It detects RTL text and only processes strings that contain RTL characters.
|
48
|
+
|
49
|
+
## Key Dependencies
|
50
|
+
|
51
|
+
- **prawn ~> 2.2**: The PDF generation library being patched
|
52
|
+
- **ffi ~> 1.15**: Foreign Function Interface for Ruby to call ICU C library functions
|
53
|
+
- **ICU library**: System dependency (libicuuc) providing Unicode Bidirectional Algorithm implementation
|
54
|
+
|
55
|
+
## Contributing
|
56
|
+
|
57
|
+
- Run tests `bundle exec rake` and RuboCop linting `bundle exec rubocop` before committing.
|
data/Gemfile
CHANGED
@@ -1,4 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
source 'https://rubygems.org'
|
2
4
|
|
3
5
|
# Specify your gem's dependencies in prawn-rtl-support.gemspec
|
4
6
|
gemspec
|
7
|
+
|
8
|
+
# Development dependencies
|
9
|
+
group :development, :test do
|
10
|
+
gem 'pry', '~> 0.12'
|
11
|
+
gem 'rake', '~> 13.0'
|
12
|
+
gem 'rspec', '~> 3.9'
|
13
|
+
gem 'rubocop', '1.80.2'
|
14
|
+
gem 'rubocop-performance', '1.25.0'
|
15
|
+
gem 'rubocop-rake', '0.7.1'
|
16
|
+
gem 'rubocop-rspec', '3.7.0'
|
17
|
+
gem 'rubocop-rubycw', '0.2.2'
|
18
|
+
gem 'rubocop-thread_safety', '0.7.3'
|
19
|
+
end
|
data/README.md
CHANGED
@@ -1,15 +1,53 @@
|
|
1
1
|
# Prawn::Rtl::Support
|
2
2
|
|
3
|
-
[](https://github.com/prawn-rtl-support/prawn-rtl-support/actions/workflows/ci.yml)
|
4
4
|
|
5
|
-
This gem
|
5
|
+
This gem provides bidirectional text support for Prawn PDF generator. It uses the Unicode Bidirectional Algorithm via [ICU](http://site.icu-project.org/) (International Components for Unicode) for text reordering and implements Arabic letter shaping similar to [Arabic Letter Connector](https://github.com/staii/arabic-letter-connector). Prawn patching is minimal - we only patch [`Prawn::Text::Formatted::Box#original_text`](https://github.com/prawnpdf/prawn/blob/master/lib/prawn/text/formatted/box.rb#L367).
|
6
|
+
|
7
|
+
## Supported Languages
|
8
|
+
|
9
|
+
- **Full support** (with contextual letter shaping):
|
10
|
+
- Arabic
|
11
|
+
- Persian/Farsi
|
12
|
+
- Urdu
|
13
|
+
- Other Arabic script languages
|
14
|
+
|
15
|
+
- **RTL support** (bidirectional text reordering):
|
16
|
+
- Hebrew
|
17
|
+
- Syriac
|
18
|
+
- Thaana
|
19
|
+
- Mixed LTR/RTL text
|
6
20
|
|
7
21
|
## Motivation
|
8
22
|
|
9
|
-
Ruby and Rails internally provide
|
23
|
+
Ruby and Rails internally provide Unicode string normalization. However, Prawn doesn't connect Arabic letters into their contextual forms and doesn't support mixed LTR and RTL strings. This gem adds this support.
|
10
24
|
|
11
|
-
##
|
12
|
-
|
25
|
+
## Requirements
|
26
|
+
|
27
|
+
### ICU Library
|
28
|
+
|
29
|
+
Starting from version 0.2.0, this gem requires the ICU library to be installed on your system:
|
30
|
+
|
31
|
+
- **Linux**: Usually pre-installed. If not, install with:
|
32
|
+
```bash
|
33
|
+
# Ubuntu/Debian
|
34
|
+
sudo apt-get install libicu-dev
|
35
|
+
|
36
|
+
# RHEL/CentOS/Fedora
|
37
|
+
sudo yum install libicu-devel
|
38
|
+
```
|
39
|
+
|
40
|
+
- **macOS**: Install via Homebrew:
|
41
|
+
```bash
|
42
|
+
brew install icu4c
|
43
|
+
```
|
44
|
+
|
45
|
+
- **Windows**: Download and install ICU from the [official site](http://site.icu-project.org/download)
|
46
|
+
|
47
|
+
- **Custom installation path**: If ICU is installed in a non-standard location, set the `ICU_LIB_PATH` environment variable:
|
48
|
+
```bash
|
49
|
+
export ICU_LIB_PATH=/custom/path/to/icu/lib
|
50
|
+
```
|
13
51
|
|
14
52
|
## Installation
|
15
53
|
|
@@ -23,24 +61,30 @@ And that's all. Your Prawn is patched!
|
|
23
61
|
|
24
62
|
Or install it yourself as:
|
25
63
|
|
26
|
-
|
64
|
+
```shell
|
65
|
+
gem install prawn-rtl-support
|
66
|
+
```
|
27
67
|
|
28
68
|
## Usage
|
29
69
|
|
30
|
-
`prawn-rtl-support` provide method [`Prawn::Rtl::Connector#fix_rtl(string)`](https://github.com/
|
70
|
+
`prawn-rtl-support` provide method [`Prawn::Rtl::Connector#fix_rtl(string)`](https://github.com/prawn-rtl-support/prawn-rtl-support/blob/master/lib/prawn/rtl/connector.rb#L13) which reverse string and connect arabic letters.
|
71
|
+
|
31
72
|
Prawn patching is minimal, we patch only [`Prawn::Text::Formatted::Box#original_text`](https://github.com/prawnpdf/prawn/blob/master/lib/prawn/text/formatted/box.rb#L367).
|
32
73
|
|
33
74
|
## Development
|
34
75
|
|
76
|
+
Check [CLAUDE.md](CLAUDE.md) for more details about the architecture and development commands.
|
77
|
+
|
35
78
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
36
79
|
|
37
80
|
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 tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
38
81
|
|
39
82
|
## Contributing
|
40
83
|
|
41
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
42
|
-
|
84
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/prawn-rtl-support/prawn-rtl-support. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
43
85
|
|
86
|
+
## Acknowledgment
|
87
|
+
This gem uses the same code as [Arabic Letter Connector](https://github.com/staii/arabic-letter-connector) by [@staii](https://github.com/staii) and therefore is based on [Arabic-Prawn](https://rubygems.org/gems/Arabic-Prawn/versions/0.0.1) by Dynamix Solutions (Ahmed Nasser).
|
44
88
|
|
45
89
|
## License
|
46
90
|
|
@@ -0,0 +1,225 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rbconfig'
|
4
|
+
require 'ffi'
|
5
|
+
|
6
|
+
module Prawn
|
7
|
+
module Rtl
|
8
|
+
# FFI BiDi wrapper for Unicode Bidirectional Algorithm support
|
9
|
+
#
|
10
|
+
# This module provides direct FFI bindings to ICU's ubidi functions
|
11
|
+
# for bidirectional text processing.
|
12
|
+
module Bidi
|
13
|
+
extend FFI::Library
|
14
|
+
|
15
|
+
class BiDiError < StandardError; end
|
16
|
+
|
17
|
+
# Detect platform
|
18
|
+
def self.platform
|
19
|
+
os = RbConfig::CONFIG['host_os']
|
20
|
+
case os
|
21
|
+
when /darwin/
|
22
|
+
:osx
|
23
|
+
when /linux/
|
24
|
+
:linux
|
25
|
+
when /bsd/
|
26
|
+
:bsd
|
27
|
+
when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
|
28
|
+
:windows
|
29
|
+
else
|
30
|
+
:linux
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Search paths for ICU libraries
|
35
|
+
def self.search_paths
|
36
|
+
@search_paths ||=
|
37
|
+
if ENV['ICU_LIB_PATH']
|
38
|
+
[ENV['ICU_LIB_PATH']]
|
39
|
+
elsif FFI::Platform::IS_WINDOWS
|
40
|
+
ENV['PATH'].split(File::PATH_SEPARATOR)
|
41
|
+
else
|
42
|
+
[
|
43
|
+
'/usr/local/{lib64,lib}',
|
44
|
+
'/opt/local/{lib64,lib}',
|
45
|
+
'/opt/homebrew/{lib64,lib}',
|
46
|
+
'/usr/{lib64,lib}'
|
47
|
+
] + Dir['/usr/lib/*-linux-gnu'] + Dir['/lib/*-linux-gnu']
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Find ICU library files
|
52
|
+
def self.find_icu_lib
|
53
|
+
candidates = []
|
54
|
+
lib_name = 'icuuc'
|
55
|
+
|
56
|
+
search_paths.each do |path|
|
57
|
+
Dir.glob(File.expand_path(path)).each do |dir|
|
58
|
+
# Try versioned libraries first (newer to older)
|
59
|
+
# ICU versions from 4.0 (2009) to potential future versions
|
60
|
+
90.downto(4).each do |version|
|
61
|
+
case platform
|
62
|
+
when :osx
|
63
|
+
candidates << File.join(dir, "lib#{lib_name}.#{version}.dylib")
|
64
|
+
candidates << File.join(dir, "lib#{lib_name}.dylib")
|
65
|
+
when :windows
|
66
|
+
candidates << File.join(dir, "#{lib_name}#{version}.dll")
|
67
|
+
candidates << File.join(dir, "#{lib_name}.dll")
|
68
|
+
else
|
69
|
+
candidates << File.join(dir, "lib#{lib_name}.so.#{version}")
|
70
|
+
candidates << File.join(dir, "lib#{lib_name}.so")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Find the first existing library
|
77
|
+
found = candidates.find { |path| File.exist?(path) }
|
78
|
+
found || ["lib#{lib_name}.so", "lib#{lib_name}.dylib", "#{lib_name}.dll", lib_name]
|
79
|
+
end
|
80
|
+
|
81
|
+
# Load the library
|
82
|
+
ffi_lib find_icu_lib
|
83
|
+
|
84
|
+
# Detect ICU version suffix by checking for a known function
|
85
|
+
def self.detect_icu_suffix
|
86
|
+
@detect_icu_suffix ||= begin
|
87
|
+
# Try common suffixes from newer to older versions
|
88
|
+
# Some versions use _4_2 format, others use _42 format
|
89
|
+
suffixes = [''] + 90.downto(4).flat_map { |v| ["_#{v}", "_#{v / 10}_#{v % 10}"] }
|
90
|
+
|
91
|
+
# Find suffix by checking if ubidi_open function exists
|
92
|
+
suffix = suffixes.find do |s|
|
93
|
+
# Try to find the function
|
94
|
+
func_name = :"ubidi_open#{s}"
|
95
|
+
ffi_libraries.any? do |lib|
|
96
|
+
lib.find_function(func_name.to_s)
|
97
|
+
end
|
98
|
+
rescue StandardError
|
99
|
+
false
|
100
|
+
end
|
101
|
+
|
102
|
+
suffix || ''
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Helper to attach function with detected suffix
|
107
|
+
def self.attach_icu_function(ruby_name, icu_name, args, return_type)
|
108
|
+
suffixed_name = "#{icu_name}#{detect_icu_suffix}"
|
109
|
+
attach_function ruby_name, suffixed_name.to_sym, args, return_type
|
110
|
+
end
|
111
|
+
|
112
|
+
# Constants from ubidi.h
|
113
|
+
UBIDI_DEFAULT_LTR = 0xfe
|
114
|
+
UBIDI_DEFAULT_RTL = 0xff
|
115
|
+
UBIDI_LTR = 0
|
116
|
+
UBIDI_RTL = 1
|
117
|
+
UBIDI_MIXED = 2
|
118
|
+
UBIDI_NEUTRAL = 3
|
119
|
+
|
120
|
+
# Reorder options
|
121
|
+
UBIDI_DO_MIRRORING = 2
|
122
|
+
UBIDI_OUTPUT_REVERSE = 16
|
123
|
+
|
124
|
+
# Attach ICU functions with version detection
|
125
|
+
attach_icu_function :ubidi_open, 'ubidi_open', [], :pointer
|
126
|
+
attach_icu_function :ubidi_close, 'ubidi_close', [:pointer], :void
|
127
|
+
attach_icu_function :ubidi_setPara, 'ubidi_setPara', %i[pointer pointer int32 uint8 pointer pointer], :void
|
128
|
+
attach_icu_function :ubidi_getDirection, 'ubidi_getDirection', [:pointer], :int
|
129
|
+
attach_icu_function :ubidi_getLength, 'ubidi_getLength', [:pointer], :int32
|
130
|
+
attach_icu_function :ubidi_writeReordered, 'ubidi_writeReordered', %i[pointer pointer int32 uint16 pointer],
|
131
|
+
:int32
|
132
|
+
attach_icu_function :ubidi_countRuns, 'ubidi_countRuns', %i[pointer pointer], :int32
|
133
|
+
|
134
|
+
# Reorders text according to the Unicode Bidirectional Algorithm
|
135
|
+
#
|
136
|
+
# @param text [String] the text to reorder
|
137
|
+
# @param direction [Symbol] :ltr, :rtl, or :auto (default)
|
138
|
+
# @return [String] the visually reordered text
|
139
|
+
def self.reorder(text, direction: :auto)
|
140
|
+
return text if text.nil? || text.empty?
|
141
|
+
|
142
|
+
# Convert direction to ubidi constant
|
143
|
+
para_level =
|
144
|
+
case direction
|
145
|
+
when :ltr then UBIDI_LTR
|
146
|
+
when :rtl then UBIDI_RTL
|
147
|
+
else UBIDI_DEFAULT_LTR
|
148
|
+
end
|
149
|
+
|
150
|
+
bidi = nil
|
151
|
+
begin
|
152
|
+
# Open BiDi object
|
153
|
+
bidi = ubidi_open
|
154
|
+
raise BiDiError, 'Failed to create BiDi object' if bidi.null?
|
155
|
+
|
156
|
+
# Convert string to UTF-16 for ICU
|
157
|
+
utf16_text = text.encode('UTF-16LE')
|
158
|
+
text_length = utf16_text.bytesize / 2
|
159
|
+
|
160
|
+
# Create buffer for UTF-16 string
|
161
|
+
text_buffer = FFI::MemoryPointer.new(:uint16, text_length + 1)
|
162
|
+
text_buffer.put_bytes(0, utf16_text)
|
163
|
+
|
164
|
+
# Error status
|
165
|
+
status = FFI::MemoryPointer.new(:int32)
|
166
|
+
status.put_int32(0, 0)
|
167
|
+
|
168
|
+
# Set paragraph
|
169
|
+
ubidi_setPara(bidi, text_buffer, text_length, para_level, nil, status)
|
170
|
+
|
171
|
+
error_code = status.get_int32(0)
|
172
|
+
raise BiDiError, "ubidi_setPara failed with error code: #{error_code}" if error_code.positive?
|
173
|
+
|
174
|
+
# Get required size for output
|
175
|
+
output_length = text_length * 2
|
176
|
+
output_buffer = FFI::MemoryPointer.new(:uint16, output_length)
|
177
|
+
|
178
|
+
# Write reordered text
|
179
|
+
written = ubidi_writeReordered(bidi, output_buffer, output_length, UBIDI_DO_MIRRORING, status)
|
180
|
+
|
181
|
+
error_code = status.get_int32(0)
|
182
|
+
raise BiDiError, "ubidi_writeReordered failed with error code: #{error_code}" if error_code.positive?
|
183
|
+
|
184
|
+
# Convert back from UTF-16 to UTF-8
|
185
|
+
result_bytes = output_buffer.get_bytes(0, written * 2)
|
186
|
+
result_bytes.force_encoding('UTF-16LE').encode('UTF-8')
|
187
|
+
ensure
|
188
|
+
ubidi_close(bidi) if bidi && !bidi.null?
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Checks if a string contains RTL characters
|
193
|
+
#
|
194
|
+
# @param text [String] the text to check
|
195
|
+
# @return [Boolean] true if the text contains RTL characters
|
196
|
+
def self.contains_rtl?(text)
|
197
|
+
return false if text.nil? || text.empty?
|
198
|
+
|
199
|
+
bidi = nil
|
200
|
+
begin
|
201
|
+
bidi = ubidi_open
|
202
|
+
return false if bidi.null?
|
203
|
+
|
204
|
+
utf16_text = text.encode('UTF-16LE')
|
205
|
+
text_length = utf16_text.bytesize / 2
|
206
|
+
|
207
|
+
text_buffer = FFI::MemoryPointer.new(:uint16, text_length + 1)
|
208
|
+
text_buffer.put_bytes(0, utf16_text)
|
209
|
+
|
210
|
+
status = FFI::MemoryPointer.new(:int32)
|
211
|
+
status.put_int32(0, 0)
|
212
|
+
|
213
|
+
ubidi_setPara(bidi, text_buffer, text_length, UBIDI_DEFAULT_LTR, nil, status)
|
214
|
+
|
215
|
+
return false if status.get_int32(0).positive?
|
216
|
+
|
217
|
+
direction = ubidi_getDirection(bidi)
|
218
|
+
[UBIDI_RTL, UBIDI_MIXED].include?(direction)
|
219
|
+
ensure
|
220
|
+
ubidi_close(bidi) if bidi && !bidi.null?
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
@@ -3,48 +3,82 @@
|
|
3
3
|
module Prawn
|
4
4
|
module Rtl
|
5
5
|
module Connector
|
6
|
+
# Handles the logic for Arabic letter connection and contextual form selection.
|
7
|
+
#
|
8
|
+
# This module implements the core algorithm for determining which form
|
9
|
+
# (isolated, initial, medial, or final) an Arabic character should take
|
10
|
+
# based on its surrounding characters. It maintains a mapping of Arabic
|
11
|
+
# Unicode characters to their various contextual forms.
|
6
12
|
module Logic
|
7
|
-
|
8
13
|
@@charinfos = nil
|
9
14
|
|
15
|
+
# Represents information about an Arabic character and its contextual forms.
|
16
|
+
#
|
17
|
+
# Each Arabic letter can have up to four different forms depending on
|
18
|
+
# its position within a word and connection to surrounding letters.
|
10
19
|
class CharacterInfo
|
11
|
-
|
20
|
+
# @return [String] the base Unicode character
|
21
|
+
attr_accessor :common
|
22
|
+
|
23
|
+
# @return [Hash] the character's forms (:isolated, :final, :initial, :medial)
|
24
|
+
attr_accessor :formatted
|
12
25
|
|
26
|
+
# Initializes a new CharacterInfo instance.
|
27
|
+
#
|
28
|
+
# @param common [String] the base Unicode character
|
29
|
+
# @param isolated [String] the isolated form of the character
|
30
|
+
# @param final [String] the final form of the character
|
31
|
+
# @param initial [String] the initial form of the character
|
32
|
+
# @param medial [String] the medial form of the character
|
33
|
+
# @param connects [Boolean] whether this character connects to the next
|
34
|
+
# @param diacritic [Boolean] whether this character is a diacritic mark
|
13
35
|
def initialize(common, isolated, final, initial, medial, connects, diacritic)
|
14
36
|
@common = common
|
15
37
|
@formatted = {
|
16
38
|
isolated: isolated,
|
17
39
|
final: final,
|
18
40
|
initial: initial,
|
19
|
-
medial: medial
|
41
|
+
medial: medial
|
20
42
|
}
|
21
43
|
@connects = connects
|
22
44
|
@diacritic = diacritic
|
23
45
|
end
|
24
46
|
|
47
|
+
# Checks if this character connects to the following character.
|
48
|
+
#
|
49
|
+
# @return [Boolean] true if the character connects forward
|
25
50
|
def connects?
|
26
51
|
@connects
|
27
52
|
end
|
28
53
|
|
54
|
+
# Checks if this character is a diacritic mark.
|
55
|
+
#
|
56
|
+
# @return [Boolean] true if the character is a diacritic
|
29
57
|
def diacritic?
|
30
58
|
@diacritic
|
31
59
|
end
|
32
|
-
|
33
60
|
end
|
34
61
|
|
35
|
-
#
|
62
|
+
# Determines the contextual form of an Arabic character.
|
63
|
+
#
|
64
|
+
# Determines the form of the current character (:isolated, :initial, :medial,
|
36
65
|
# or :final), given the previous character and the next one. In Arabic, all
|
37
66
|
# characters can connect with a previous character, but not all letters can
|
38
|
-
# connect with the next character (this is determined by
|
39
|
-
#
|
67
|
+
# connect with the next character (this is determined by CharacterInfo#connects?).
|
68
|
+
#
|
69
|
+
# @param previous_previous_char [String, nil] the character two positions before
|
70
|
+
# @param previous_char [String, nil] the character immediately before
|
71
|
+
# @param next_char [String, nil] the character immediately after
|
72
|
+
# @param next_next_char [String, nil] the character two positions after
|
73
|
+
# @return [Symbol] the contextual form (:isolated, :initial, :medial, or :final)
|
40
74
|
def self.determine_form(previous_previous_char, previous_char, next_char, next_next_char)
|
41
75
|
charinfos = self.charinfos
|
42
76
|
next_char = next_next_char if charinfos[next_char] && charinfos[next_char].diacritic?
|
43
77
|
previous_char = previous_previous_char if charinfos[previous_char] && charinfos[previous_char].diacritic?
|
44
78
|
if charinfos[previous_char] && charinfos[next_char]
|
45
79
|
charinfos[previous_char].connects? ? :medial : :initial # If the current character does not connect,
|
46
|
-
|
47
|
-
|
80
|
+
# its medial form will map to its final form,
|
81
|
+
# and its initial form will map to its isolated form.
|
48
82
|
elsif charinfos[previous_char] # The next character is not an arabic character.
|
49
83
|
charinfos[previous_char].connects? ? :final : :isolated
|
50
84
|
elsif charinfos[next_char] # The previous character is not an arabic character.
|
@@ -54,6 +88,14 @@ module Prawn
|
|
54
88
|
end
|
55
89
|
end
|
56
90
|
|
91
|
+
# Transforms Arabic text by applying contextual letter forms.
|
92
|
+
#
|
93
|
+
# Processes a string character by character, determining the appropriate
|
94
|
+
# contextual form for each Arabic letter based on its surrounding characters.
|
95
|
+
# Non-Arabic characters pass through unchanged.
|
96
|
+
#
|
97
|
+
# @param str [String] the text to transform
|
98
|
+
# @return [String] the transformed text with Arabic letters in their contextual forms
|
57
99
|
def self.transform(str)
|
58
100
|
res = ''
|
59
101
|
charinfos = self.charinfos
|
@@ -69,7 +111,8 @@ module Prawn
|
|
69
111
|
next_char = next_next_char
|
70
112
|
next_next_char = char
|
71
113
|
return unless current_char
|
72
|
-
|
114
|
+
|
115
|
+
if charinfos.key?(current_char)
|
73
116
|
form = determine_form(previous_previous_char, previous_char, next_char, next_next_char)
|
74
117
|
res += charinfos[current_char].formatted[form]
|
75
118
|
else
|
@@ -78,13 +121,19 @@ module Prawn
|
|
78
121
|
end
|
79
122
|
str.each_char { |char| consume_character.call(char) }
|
80
123
|
2.times { consume_character.call(nil) }
|
81
|
-
|
124
|
+
res
|
82
125
|
end
|
83
126
|
|
84
|
-
|
85
|
-
|
127
|
+
# Returns the character information mapping for Arabic characters.
|
128
|
+
#
|
129
|
+
# Lazily initializes and returns a hash mapping Arabic Unicode characters
|
130
|
+
# to their CharacterInfo objects containing contextual forms.
|
131
|
+
#
|
132
|
+
# @return [Hash{String => CharacterInfo}] the character information mapping
|
133
|
+
# @api private
|
86
134
|
def self.charinfos
|
87
135
|
return @@charinfos unless @@charinfos.nil?
|
136
|
+
|
88
137
|
@@charinfos = {}
|
89
138
|
add('0627', 'fe8d', 'fe8e', 'fe8d', 'fe8e', false) # Alef
|
90
139
|
add('0628', 'fe8f', 'fe90', 'fe91', 'fe92', true) # Ba2
|
@@ -135,6 +184,16 @@ module Prawn
|
|
135
184
|
@@charinfos
|
136
185
|
end
|
137
186
|
|
187
|
+
# Adds a character and its contextual forms to the character mapping.
|
188
|
+
#
|
189
|
+
# @param common [String] hex code of the base Unicode character
|
190
|
+
# @param isolated [String] hex code of the isolated form
|
191
|
+
# @param final [String] hex code of the final form
|
192
|
+
# @param initial [String] hex code of the initial form
|
193
|
+
# @param medial [String] hex code of the medial form
|
194
|
+
# @param connects [Boolean] whether this character connects to the next
|
195
|
+
# @param diacritic [Boolean] whether this character is a diacritic mark
|
196
|
+
# @api private
|
138
197
|
def self.add(common, isolated, final, initial, medial, connects, diacritic = false)
|
139
198
|
charinfo = CharacterInfo.new(
|
140
199
|
[common.hex].pack('U'),
|
@@ -147,7 +206,6 @@ module Prawn
|
|
147
206
|
)
|
148
207
|
@@charinfos[charinfo.common] = charinfo
|
149
208
|
end
|
150
|
-
|
151
209
|
end
|
152
210
|
end
|
153
211
|
end
|
data/lib/prawn/rtl/connector.rb
CHANGED
@@ -1,32 +1,69 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
require_relative 'connector/logic'
|
4
|
+
require_relative 'bidi'
|
5
5
|
|
6
6
|
module Prawn
|
7
7
|
module Rtl
|
8
|
+
# Provides bidirectional text support and Arabic letter connection for Prawn PDF generation.
|
9
|
+
#
|
10
|
+
# This module handles RTL (Right-to-Left) text processing by:
|
11
|
+
# - Connecting Arabic script letters according to their contextual forms
|
12
|
+
# - Reordering text using the Unicode Bidirectional Algorithm
|
13
|
+
# - Supporting multiple RTL languages (Arabic, Hebrew, Persian, Urdu, etc.)
|
14
|
+
# - Handling mixed LTR/RTL text properly
|
15
|
+
#
|
16
|
+
# @example Fix Arabic text for PDF rendering
|
17
|
+
# text = "مرحبا بالعالم"
|
18
|
+
# fixed_text = Prawn::Rtl::Connector.fix_rtl(text)
|
19
|
+
#
|
20
|
+
# @example Fix Hebrew text for PDF rendering
|
21
|
+
# text = "שלום עולם"
|
22
|
+
# fixed_text = Prawn::Rtl::Connector.fix_rtl(text)
|
23
|
+
#
|
24
|
+
# @example Fix mixed LTR/RTL text
|
25
|
+
# text = "Hello مرحبا World"
|
26
|
+
# fixed_text = Prawn::Rtl::Connector.fix_rtl(text)
|
8
27
|
module Connector
|
28
|
+
# Connects Arabic letters according to their contextual forms.
|
29
|
+
#
|
30
|
+
# @param string [String] the text containing Arabic letters to connect
|
31
|
+
# @return [String] the text with properly connected Arabic letters
|
9
32
|
def self.connect(string)
|
10
33
|
Prawn::Rtl::Connector::Logic.transform(string)
|
11
34
|
end
|
12
35
|
|
36
|
+
# Fixes RTL text by connecting Arabic letters and reordering for visual display.
|
37
|
+
#
|
38
|
+
# This is the main entry point for processing RTL text. It detects if the text
|
39
|
+
# contains RTL characters and applies both letter connection and bidirectional
|
40
|
+
# reordering if needed.
|
41
|
+
#
|
42
|
+
# @param string [String] the text to process
|
43
|
+
# @return [String] the processed text ready for PDF rendering
|
13
44
|
def self.fix_rtl(string)
|
14
45
|
return string unless include_rtl?(string)
|
46
|
+
|
15
47
|
reorder(connect(string))
|
16
48
|
end
|
17
49
|
|
50
|
+
# Reorders text according to the Unicode Bidirectional Algorithm.
|
51
|
+
#
|
52
|
+
# Uses ICU's BiDi implementation via FFI to visually reorder mixed
|
53
|
+
# LTR/RTL text for correct display.
|
54
|
+
#
|
55
|
+
# @param string [String] the text to reorder
|
56
|
+
# @return [String] the visually reordered text
|
18
57
|
def self.reorder(string)
|
19
|
-
|
20
|
-
.from_string(string, direction: :RTL)
|
21
|
-
.reorder_visually!
|
22
|
-
.to_s
|
58
|
+
Bidi.reorder(string, direction: :rtl)
|
23
59
|
end
|
24
60
|
|
61
|
+
# Checks if a string contains RTL (Right-to-Left) characters.
|
62
|
+
#
|
63
|
+
# @param string [String] the text to check
|
64
|
+
# @return [Boolean] true if the text contains RTL characters, false otherwise
|
25
65
|
def self.include_rtl?(string)
|
26
|
-
|
27
|
-
.from_string(string)
|
28
|
-
.types
|
29
|
-
.include?(:R)
|
66
|
+
Bidi.contains_rtl?(string)
|
30
67
|
end
|
31
68
|
end
|
32
69
|
end
|
data/lib/prawn/rtl/support.rb
CHANGED
@@ -1,18 +1,32 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'pdf/core/text'
|
4
|
-
|
5
|
-
|
4
|
+
require_relative 'support/version'
|
5
|
+
require_relative 'connector'
|
6
6
|
|
7
7
|
module Prawn
|
8
8
|
module Rtl
|
9
|
+
# Main module for RTL support functionality.
|
9
10
|
module Support
|
11
|
+
# Patch module that intercepts Prawn's text rendering to apply RTL transformations.
|
12
|
+
#
|
13
|
+
# This module is prepended to Prawn::Text::Formatted::Box to automatically
|
14
|
+
# process RTL text before rendering. It intercepts the original_text method
|
15
|
+
# and applies Arabic letter connection and bidirectional text reordering
|
16
|
+
# to any text fragments that contain RTL characters.
|
17
|
+
#
|
18
|
+
# @example How it works internally
|
19
|
+
# # When Prawn renders text, this patch:
|
20
|
+
# # 1. Intercepts the text fragments
|
21
|
+
# # 2. Applies RTL fixes to each fragment
|
22
|
+
# # 3. Returns the processed fragments for rendering
|
10
23
|
module PrawnTextPatch
|
24
|
+
# Overrides the original_text method to apply RTL transformations.
|
25
|
+
#
|
26
|
+
# @return [Array<Hash>] array of text fragments with RTL text properly formatted
|
11
27
|
def original_text
|
12
28
|
super.map do |h|
|
13
|
-
if h.key?(:text)
|
14
|
-
h[:text] = Prawn::Rtl::Connector.fix_rtl(h[:text])
|
15
|
-
end
|
29
|
+
h[:text] = Prawn::Rtl::Connector.fix_rtl(h[:text]) if h.key?(:text)
|
16
30
|
h
|
17
31
|
end
|
18
32
|
end
|
data/prawn-rtl-support.gemspec
CHANGED
@@ -1,33 +1,43 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
|
4
|
-
require 'prawn/rtl/support/version'
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lib/prawn/rtl/support/version'
|
5
4
|
|
6
5
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name =
|
6
|
+
spec.name = 'prawn-rtl-support'
|
8
7
|
spec.version = Prawn::Rtl::Support::VERSION
|
9
|
-
spec.authors = [
|
10
|
-
spec.email = [
|
8
|
+
spec.authors = ['Oleksandr Lapchenko', 'Oleksii Leonov']
|
9
|
+
spec.email = ['ozeron@me.com', 'mail@oleksiileonov.com']
|
11
10
|
|
12
|
-
spec.summary = '
|
13
|
-
spec.description = '
|
14
|
-
|
15
|
-
|
11
|
+
spec.summary = 'Bidirectional text support for Prawn PDF generator'
|
12
|
+
spec.description = 'Adds right-to-left (RTL) text support to Prawn PDF generator. ' \
|
13
|
+
'Fully supports Arabic script languages (Arabic, Persian, Urdu) with ' \
|
14
|
+
'contextual letter shaping and ligatures. Also supports Hebrew and other ' \
|
15
|
+
'RTL languages with bidirectional text reordering. Handles mixed LTR/RTL text properly.'
|
16
|
+
spec.homepage = 'https://github.com/prawn-rtl-support/prawn-rtl-support'
|
17
|
+
spec.license = 'MIT'
|
18
|
+
spec.required_ruby_version = '>= 2.7.0'
|
16
19
|
|
17
|
-
spec.
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
spec.metadata = {
|
21
|
+
'rubygems_mfa_required' => 'true',
|
22
|
+
'source_code_uri' => 'https://github.com/prawn-rtl-support/prawn-rtl-support',
|
23
|
+
'changelog_uri' => 'https://github.com/prawn-rtl-support/prawn-rtl-support/blob/main/CHANGELOG.md',
|
24
|
+
'bug_tracker_uri' => 'https://github.com/prawn-rtl-support/prawn-rtl-support/issues',
|
25
|
+
'documentation_uri' => 'https://rubydoc.info/gems/prawn-rtl-support'
|
26
|
+
}
|
23
27
|
|
24
|
-
|
25
|
-
spec.
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
28
|
+
# Specify which files should be included in the gem
|
29
|
+
spec.files = (
|
30
|
+
Dir['{lib,exe}/**/*'] +
|
31
|
+
Dir['*.{md,txt,gemspec}'] +
|
32
|
+
%w[Gemfile LICENSE.txt README.md CODE_OF_CONDUCT.md].select { |f| File.exist?(f) }
|
33
|
+
).reject { |f| File.directory?(f) }
|
34
|
+
|
35
|
+
spec.bindir = 'exe'
|
36
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
37
|
+
spec.require_paths = ['lib']
|
38
|
+
spec.extra_rdoc_files = ['README.md', 'LICENSE.txt']
|
30
39
|
|
40
|
+
# Runtime dependencies
|
41
|
+
spec.add_dependency 'ffi', '~> 1.17'
|
31
42
|
spec.add_dependency 'prawn', '~> 2.2'
|
32
|
-
spec.add_dependency 'twitter_cldr', '>= 4.0', '< 7.0'
|
33
43
|
end
|
metadata
CHANGED
@@ -1,99 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: prawn-rtl-support
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Oleksandr Lapchenko
|
8
|
-
|
8
|
+
- Oleksii Leonov
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: ffi
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
20
|
-
type: :
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - "~>"
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '13.0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: rspec
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '3.9'
|
34
|
-
type: :development
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '3.9'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: pry
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - "~>"
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '0.12'
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '0.12'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: rubocop
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - "~>"
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '0.77'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - "~>"
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '0.77'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: rubocop-performance
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - "~>"
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '1.5'
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - "~>"
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '1.5'
|
83
|
-
- !ruby/object:Gem::Dependency
|
84
|
-
name: rubocop-rspec
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
86
|
-
requirements:
|
87
|
-
- - "~>"
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: '1.37'
|
90
|
-
type: :development
|
19
|
+
version: '1.17'
|
20
|
+
type: :runtime
|
91
21
|
prerelease: false
|
92
22
|
version_requirements: !ruby/object:Gem::Requirement
|
93
23
|
requirements:
|
94
24
|
- - "~>"
|
95
25
|
- !ruby/object:Gem::Version
|
96
|
-
version: '1.
|
26
|
+
version: '1.17'
|
97
27
|
- !ruby/object:Gem::Dependency
|
98
28
|
name: prawn
|
99
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,53 +38,40 @@ dependencies:
|
|
108
38
|
- - "~>"
|
109
39
|
- !ruby/object:Gem::Version
|
110
40
|
version: '2.2'
|
111
|
-
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
- - ">="
|
116
|
-
- !ruby/object:Gem::Version
|
117
|
-
version: '4.0'
|
118
|
-
- - "<"
|
119
|
-
- !ruby/object:Gem::Version
|
120
|
-
version: '7.0'
|
121
|
-
type: :runtime
|
122
|
-
prerelease: false
|
123
|
-
version_requirements: !ruby/object:Gem::Requirement
|
124
|
-
requirements:
|
125
|
-
- - ">="
|
126
|
-
- !ruby/object:Gem::Version
|
127
|
-
version: '4.0'
|
128
|
-
- - "<"
|
129
|
-
- !ruby/object:Gem::Version
|
130
|
-
version: '7.0'
|
131
|
-
description: Add suport for arabic language in prawn.
|
41
|
+
description: Adds right-to-left (RTL) text support to Prawn PDF generator. Fully supports
|
42
|
+
Arabic script languages (Arabic, Persian, Urdu) with contextual letter shaping and
|
43
|
+
ligatures. Also supports Hebrew and other RTL languages with bidirectional text
|
44
|
+
reordering. Handles mixed LTR/RTL text properly.
|
132
45
|
email:
|
133
46
|
- ozeron@me.com
|
47
|
+
- mail@oleksiileonov.com
|
134
48
|
executables: []
|
135
49
|
extensions: []
|
136
|
-
extra_rdoc_files:
|
50
|
+
extra_rdoc_files:
|
51
|
+
- LICENSE.txt
|
52
|
+
- README.md
|
137
53
|
files:
|
138
|
-
-
|
139
|
-
-
|
140
|
-
- ".travis.yml"
|
54
|
+
- CHANGELOG.md
|
55
|
+
- CLAUDE.md
|
141
56
|
- CODE_OF_CONDUCT.md
|
142
57
|
- Gemfile
|
143
58
|
- LICENSE.txt
|
144
59
|
- README.md
|
145
|
-
-
|
146
|
-
- bin/console
|
147
|
-
- bin/setup
|
60
|
+
- lib/prawn/rtl/bidi.rb
|
148
61
|
- lib/prawn/rtl/connector.rb
|
149
62
|
- lib/prawn/rtl/connector/logic.rb
|
150
63
|
- lib/prawn/rtl/support.rb
|
151
64
|
- lib/prawn/rtl/support/version.rb
|
152
65
|
- prawn-rtl-support.gemspec
|
153
|
-
homepage: https://github.com/
|
66
|
+
homepage: https://github.com/prawn-rtl-support/prawn-rtl-support
|
154
67
|
licenses:
|
155
68
|
- MIT
|
156
|
-
metadata:
|
157
|
-
|
69
|
+
metadata:
|
70
|
+
rubygems_mfa_required: 'true'
|
71
|
+
source_code_uri: https://github.com/prawn-rtl-support/prawn-rtl-support
|
72
|
+
changelog_uri: https://github.com/prawn-rtl-support/prawn-rtl-support/blob/main/CHANGELOG.md
|
73
|
+
bug_tracker_uri: https://github.com/prawn-rtl-support/prawn-rtl-support/issues
|
74
|
+
documentation_uri: https://rubydoc.info/gems/prawn-rtl-support
|
158
75
|
rdoc_options: []
|
159
76
|
require_paths:
|
160
77
|
- lib
|
@@ -162,15 +79,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
162
79
|
requirements:
|
163
80
|
- - ">="
|
164
81
|
- !ruby/object:Gem::Version
|
165
|
-
version:
|
82
|
+
version: 2.7.0
|
166
83
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
167
84
|
requirements:
|
168
85
|
- - ">="
|
169
86
|
- !ruby/object:Gem::Version
|
170
87
|
version: '0'
|
171
88
|
requirements: []
|
172
|
-
rubygems_version: 3.
|
173
|
-
signing_key:
|
89
|
+
rubygems_version: 3.6.7
|
174
90
|
specification_version: 4
|
175
|
-
summary:
|
91
|
+
summary: Bidirectional text support for Prawn PDF generator
|
176
92
|
test_files: []
|
data/.gitignore
DELETED
data/.rspec
DELETED
data/.travis.yml
DELETED
data/Rakefile
DELETED
data/bin/console
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require "bundler/setup"
|
4
|
-
require "prawn/rtl/support"
|
5
|
-
|
6
|
-
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
-
# with your gem easier. You can also use a different console, if you like.
|
8
|
-
|
9
|
-
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
-
# require "pry"
|
11
|
-
# Pry.start
|
12
|
-
|
13
|
-
require "pry"
|
14
|
-
Pry.start(__FILE__)
|