zpl-transformer 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +35 -0
- data/LICENSE.txt +21 -0
- data/README.md +129 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/zpl-transformer/command.rb +19 -0
- data/lib/zpl-transformer/font.rb +98 -0
- data/lib/zpl-transformer/reader.rb +87 -0
- data/lib/zpl-transformer/transformer/base.rb +50 -0
- data/lib/zpl-transformer/transformer/base_scaler.rb +20 -0
- data/lib/zpl-transformer/transformer/font_scaler.rb +148 -0
- data/lib/zpl-transformer/transformer/generic_scaler.rb +81 -0
- data/lib/zpl-transformer/transformer/pipeline.rb +20 -0
- data/lib/zpl-transformer/transformers.rb +3 -0
- data/lib/zpl-transformer/version.rb +3 -0
- data/lib/zpl-transformer.rb +16 -0
- data/zpl-transformer.gemspec +26 -0
- metadata +108 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: aacd8a6370c188d59d840059ab0640844b1ce490
|
4
|
+
data.tar.gz: ba144f793ed25e8620f769ab9bf1d80e6f8bec78
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 753c6b8a358b0224d36ad0e53a6c8aa3b8771e5aba7898c4d7d5189206e32f8b0b2876176573c47ef6d64c5c583121ed2bba4fdaf5cbd4be8cb8825699417e81
|
7
|
+
data.tar.gz: 33b9326769e0b0f182333661ead968e1b058695c494a5f88c5cedd413eb202768fb265c4c8880ad97b757412a0f336d6f8e7f7d71a1aca78654e9ce6fd8f93c8
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
zpl-transformer (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
diff-lcs (1.3)
|
10
|
+
rake (10.5.0)
|
11
|
+
rspec (3.8.0)
|
12
|
+
rspec-core (~> 3.8.0)
|
13
|
+
rspec-expectations (~> 3.8.0)
|
14
|
+
rspec-mocks (~> 3.8.0)
|
15
|
+
rspec-core (3.8.0)
|
16
|
+
rspec-support (~> 3.8.0)
|
17
|
+
rspec-expectations (3.8.2)
|
18
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
19
|
+
rspec-support (~> 3.8.0)
|
20
|
+
rspec-mocks (3.8.0)
|
21
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
22
|
+
rspec-support (~> 3.8.0)
|
23
|
+
rspec-support (3.8.0)
|
24
|
+
|
25
|
+
PLATFORMS
|
26
|
+
ruby
|
27
|
+
|
28
|
+
DEPENDENCIES
|
29
|
+
bundler (~> 2.0)
|
30
|
+
rake (~> 10.0)
|
31
|
+
rspec (~> 3.0)
|
32
|
+
zpl-transformer!
|
33
|
+
|
34
|
+
BUNDLED WITH
|
35
|
+
2.0.1
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2018 Benoit de Chezelles
|
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,129 @@
|
|
1
|
+
# ZplTransformer
|
2
|
+
|
3
|
+
## Why
|
4
|
+
|
5
|
+
Our Zebra printer has a 300dpi resolution, and can only handle ZPL that is to be printed by a 300dpi printer.
|
6
|
+
|
7
|
+
Some shipping API are not configurable enough, and can return ZPL code that is to be printed by a 203dpi printer.
|
8
|
+
|
9
|
+
We needed a way to scale a ZPL code from 203dpi to 300dpi.
|
10
|
+
|
11
|
+
|
12
|
+
## How it works
|
13
|
+
|
14
|
+
It works by parsing the ZPL commands and transform them through one or more transformers.
|
15
|
+
|
16
|
+
The ZPL command parser reads commands one by one, ignoring newlines and comments between each command.
|
17
|
+
NOTE: the parser assumes the control char to be `^`, it doesn't support the command `^CC` to change the control char.
|
18
|
+
|
19
|
+
A transformer is a class inheriting from `Zpl::Transformer::Base` and implementing the method `map_cmd(cmd)`
|
20
|
+
|
21
|
+
TODO explain how a transformer is applied? & how to implem' a Transformer (takes a Zpl::Command, and should return a Zpl::Command, can return nil to delete a cmd)
|
22
|
+
|
23
|
+
|
24
|
+
## Available transformers
|
25
|
+
|
26
|
+
### `Zpl::Transformer::GenericScaler`
|
27
|
+
|
28
|
+
This transformer can scale to a given ratio, specific arguments of a set of commands.
|
29
|
+
|
30
|
+
Example:
|
31
|
+
|
32
|
+
```rb
|
33
|
+
code = '^XA^GB12,30,2,B^XZ',
|
34
|
+
scaler = Zpl::Transformer::GenericScaler.new(1.2) # ratio: 1.2
|
35
|
+
puts scaler.apply(code) # => "^XA^GB14,36,2,B^XZ"
|
36
|
+
```
|
37
|
+
|
38
|
+
List of commands that can be scaled currently:
|
39
|
+
|
40
|
+
- `^MN` - Media Tracking
|
41
|
+
- `^BY` - Bar Code Field Default
|
42
|
+
- `^FO` - Field Origin
|
43
|
+
- `^B2` - Interleaved 2 of 5 Bar Code
|
44
|
+
- `^GB` - Graphic Box
|
45
|
+
- `^BC` - Code 128 Bar Code (Subsets A, B, and C)
|
46
|
+
|
47
|
+
These commands have at least one argument that is a coordinate (in dots) and need to be recalculated (scaled).
|
48
|
+
The non-coordinate arguments and all other commands not listed above are not touched and left as is.
|
49
|
+
|
50
|
+
This transformer only scales a few ZPL commands we needed, feel free to send a PR to add support for a ZPL command you need to be scaled.
|
51
|
+
|
52
|
+
Note: images that are embedded in the ZPL code are not supported.
|
53
|
+
|
54
|
+
|
55
|
+
### `Zpl::Transformer::FontScaler`
|
56
|
+
|
57
|
+
This transformer can scale font commands (`^A` and `^CF`) to a given ratio.
|
58
|
+
|
59
|
+
The ZPL2 specification has two kind of fonts available: scalable and bitmap.
|
60
|
+
- The scalable fonts don't have a fixed size, they can be easily scaled.
|
61
|
+
- The bitmap fonts have a fixed base size (e.g: font `A` base `height`/`width` is `9`/`5` dots). They can only be scaled to a multiple of their font's base height/width.
|
62
|
+
This transformer implements a special algorithm to try to scale bitmap fonts more precisely, but keep in mind that it is inaccurate compared to a scalable font.
|
63
|
+
|
64
|
+
#### What's so special about bitmap fonts
|
65
|
+
|
66
|
+
Given the font command `^ACR,9,9`, this will set the font `C` only for the next `^FD` field, with a field orientation `R` (rotated 90°) and a size of `9x9` dots for height and width.
|
67
|
+
However the resulting font size will not be `9x9` dots as the base size of font `C` is `18x10`. The resulting size will be `18x10` since it must be a multiple of the base size of the font.
|
68
|
+
Similarly, if the input size is `20x15` the resulting size would be `36x20`.
|
69
|
+
|
70
|
+
#### Scaling algorithm for bitmap fonts
|
71
|
+
|
72
|
+
1. Normalize the given font height to the font's base height (e.g: size `20` becomes `36`).
|
73
|
+
2. Find smallest font that can be the same size as the given font (e.g: font `A`'s base size is half of font `C`'s base size: `9x5` vs `18x10`). A smaller font means more result granularity when scaling.
|
74
|
+
3. Scale the normalized given height to the given ratio.
|
75
|
+
4. Compute the scaled width from the scaled height based on proportion of new font's base size.
|
76
|
+
5. Normalize again the scaled sizes to the new font's base sizes.
|
77
|
+
|
78
|
+
|
79
|
+
### `Zpl::Transformer::Pipeline`
|
80
|
+
|
81
|
+
This transformer allows composition of other transformers. It doesn't do anything on its own and allows you to apply multiple transformers in a row. Each transformer's output cmd is given as the input cmd of the next transformer unless the output is `nil`, in which case the cmd is skipped and following transformers are not called.
|
82
|
+
|
83
|
+
Example: This will double the scale of `code`, by first applying the **generic scaler** then the **font scaler**.
|
84
|
+
```rb
|
85
|
+
code = '^XA^GB12,30,2,B^AA,9,5^XZ',
|
86
|
+
pipeline = Zpl::Transformer::Pipeline.new([
|
87
|
+
Zpl::Transformer::GenericScaler.new(2.0),
|
88
|
+
Zpl::Transformer::FontScaler.new(2.0),
|
89
|
+
])
|
90
|
+
pipeline.apply(code) # => "^XA^GB24,60,2,B^AA,18,10^XZ"
|
91
|
+
```
|
92
|
+
|
93
|
+
|
94
|
+
## Installation
|
95
|
+
|
96
|
+
Add this line to your application's Gemfile:
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
gem 'zpl-transformer'
|
100
|
+
```
|
101
|
+
|
102
|
+
And then execute:
|
103
|
+
|
104
|
+
```sh
|
105
|
+
$ bundle
|
106
|
+
```
|
107
|
+
|
108
|
+
Or install it yourself as:
|
109
|
+
|
110
|
+
```sh
|
111
|
+
$ gem install zpl-transformer
|
112
|
+
```
|
113
|
+
|
114
|
+
|
115
|
+
## Development
|
116
|
+
|
117
|
+
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.
|
118
|
+
|
119
|
+
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).
|
120
|
+
|
121
|
+
|
122
|
+
## Contributing
|
123
|
+
|
124
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/cheerz/zpl-transformer.
|
125
|
+
|
126
|
+
|
127
|
+
## License
|
128
|
+
|
129
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "zpl/scaler"
|
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 "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module Zpl
|
2
|
+
|
3
|
+
# Data class that holds the name & params of a ZPL command.
|
4
|
+
class Command
|
5
|
+
attr_accessor :name
|
6
|
+
attr_accessor :params
|
7
|
+
|
8
|
+
def initialize(name, params = [])
|
9
|
+
@name = name
|
10
|
+
@params = params
|
11
|
+
end
|
12
|
+
|
13
|
+
# Converts the command to a ZPL string.
|
14
|
+
def to_zpl_string
|
15
|
+
"^#{ @name }#{ @params.join(',') }"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Zpl
|
2
|
+
|
3
|
+
class Font
|
4
|
+
|
5
|
+
attr_accessor :name, :base_height, :base_width, :type
|
6
|
+
|
7
|
+
def initialize(name:, base_height:, base_width:, type:, scalable: false)
|
8
|
+
@name = name
|
9
|
+
@base_height = base_height
|
10
|
+
@base_width = base_width
|
11
|
+
@type = type
|
12
|
+
@scalable = scalable
|
13
|
+
end
|
14
|
+
|
15
|
+
def base_size
|
16
|
+
[@base_height, @base_width]
|
17
|
+
end
|
18
|
+
|
19
|
+
def scalable?
|
20
|
+
@scalable
|
21
|
+
end
|
22
|
+
|
23
|
+
# Font matrix
|
24
|
+
#
|
25
|
+
# FONT HxW (dots) TYPE
|
26
|
+
#
|
27
|
+
## Bitmap fonts
|
28
|
+
# A 9 X 5 U-L-D
|
29
|
+
# B 11 X 7 U
|
30
|
+
# C, D 18 X 10 U-L-D
|
31
|
+
# E 42 x 20 OCR-B
|
32
|
+
# F 26 x 13 U-L-D
|
33
|
+
# G 60 x 40 U-L-D
|
34
|
+
# H 34 x 22 OCR-A
|
35
|
+
# P 20 x 18 U-L-D
|
36
|
+
# Q 28 x 24 U-L-D
|
37
|
+
# R 35 x 31 U-L-D
|
38
|
+
# S 40 x 35 U-L-D
|
39
|
+
# T 48 x 42 U-L-D
|
40
|
+
# U 59 x 53 U-L-D
|
41
|
+
# V 80 x 71 U-L-D
|
42
|
+
#
|
43
|
+
## Scalable font
|
44
|
+
# 0 Default: 15 x 12 U-L-D
|
45
|
+
|
46
|
+
AVAILABLE_FONTS = {
|
47
|
+
"A" => Font.new(name: "A", base_height: 9, base_width: 5, type: :u_l_d),
|
48
|
+
"B" => Font.new(name: "B", base_height: 11, base_width: 7, type: :u),
|
49
|
+
"C" => Font.new(name: "C", base_height: 18, base_width: 10, type: :u_l_d),
|
50
|
+
"D" => Font.new(name: "D", base_height: 18, base_width: 10, type: :u_l_d),
|
51
|
+
"E" => Font.new(name: "E", base_height: 42, base_width: 20, type: :ocr_b),
|
52
|
+
"F" => Font.new(name: "F", base_height: 26, base_width: 13, type: :u_l_d),
|
53
|
+
"G" => Font.new(name: "G", base_height: 60, base_width: 40, type: :u_l_d),
|
54
|
+
"H" => Font.new(name: "H", base_height: 34, base_width: 22, type: :ocr_a),
|
55
|
+
"P" => Font.new(name: "P", base_height: 20, base_width: 18, type: :u_l_d),
|
56
|
+
"Q" => Font.new(name: "Q", base_height: 28, base_width: 24, type: :u_l_d),
|
57
|
+
"R" => Font.new(name: "R", base_height: 35, base_width: 31, type: :u_l_d),
|
58
|
+
"S" => Font.new(name: "S", base_height: 40, base_width: 35, type: :u_l_d),
|
59
|
+
"T" => Font.new(name: "T", base_height: 48, base_width: 42, type: :u_l_d),
|
60
|
+
"U" => Font.new(name: "U", base_height: 59, base_width: 53, type: :u_l_d),
|
61
|
+
"V" => Font.new(name: "V", base_height: 80, base_width: 71, type: :u_l_d),
|
62
|
+
"0" => Font.new(name: "0", base_height: 15, base_width: 12, type: :u_l_d, scalable: true),
|
63
|
+
}
|
64
|
+
|
65
|
+
def self.all
|
66
|
+
AVAILABLE_FONTS.values
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.from_name?(font_name)
|
70
|
+
AVAILABLE_FONTS[font_name]
|
71
|
+
end
|
72
|
+
|
73
|
+
# Normalize *height* and *width* to the font's height and width.
|
74
|
+
def normalize_size(height:, width:)
|
75
|
+
[
|
76
|
+
normalize_single_size(height, ref_size: base_height),
|
77
|
+
normalize_single_size(width, ref_size: base_width),
|
78
|
+
]
|
79
|
+
end
|
80
|
+
|
81
|
+
# Normalize *input_size* to be a multiple of *ref_size*.
|
82
|
+
#
|
83
|
+
# Example: (with ref_size = 10)
|
84
|
+
# input_size normalized
|
85
|
+
# 0 10
|
86
|
+
# 7 10
|
87
|
+
# 10 10
|
88
|
+
# 11 20
|
89
|
+
def normalize_single_size(input_size, ref_size:)
|
90
|
+
if input_size % ref_size == 0
|
91
|
+
input_size
|
92
|
+
else
|
93
|
+
((input_size / ref_size) + 1) * ref_size
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
require 'set'
|
3
|
+
require_relative './command'
|
4
|
+
|
5
|
+
module Zpl
|
6
|
+
# NOTE: doesn't handle ZPL that changes the control char (default is assumed: '^')
|
7
|
+
class Reader
|
8
|
+
|
9
|
+
# Creates a new reader that will read ZPL commands from *content* string.
|
10
|
+
def initialize(content, strip_spaces: true)
|
11
|
+
@scanner = StringScanner.new content
|
12
|
+
@strip_spaces = strip_spaces
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns the next zpl command or ignored string if any, or nil.
|
16
|
+
#
|
17
|
+
# The ignored string can be a newlines, ignored chars, spaces.
|
18
|
+
def next_token
|
19
|
+
return if @scanner.eos?
|
20
|
+
|
21
|
+
if chars_to_ignore = parse_ignore_chars
|
22
|
+
return chars_to_ignore
|
23
|
+
end
|
24
|
+
|
25
|
+
parse_zpl_cmd
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns the next zpl command if any, or nil.
|
29
|
+
def next_command
|
30
|
+
while token = next_token
|
31
|
+
if token.is_a?(Command)
|
32
|
+
return token
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
# Yields each ZPL command to the block. Stops when there are no more commands to read.
|
40
|
+
def each_command
|
41
|
+
while cmd = next_command
|
42
|
+
yield cmd
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
def parse_zpl_cmd
|
49
|
+
# Example format: ^XXparam1,param2,,param4
|
50
|
+
# Command name: XX (the command is read as 2 chars, no more no less)
|
51
|
+
# 4 (5) params (param 3 & 5 are not given)
|
52
|
+
#
|
53
|
+
# The command stops at the next `^` or newline.
|
54
|
+
@scanner.scan(/\^([A-Z0-9@]{2})([^\^\n]*)/)
|
55
|
+
|
56
|
+
cmd_name = @scanner[1]
|
57
|
+
raw_params = @scanner[2]
|
58
|
+
if @strip_spaces
|
59
|
+
raw_params = raw_params&.strip
|
60
|
+
end
|
61
|
+
|
62
|
+
params = raw_params&.split(',', -1) || []
|
63
|
+
if @strip_spaces
|
64
|
+
params.each { |param| param.strip! }
|
65
|
+
end
|
66
|
+
Command.new(cmd_name, params)
|
67
|
+
end
|
68
|
+
|
69
|
+
def parse_ignore_chars
|
70
|
+
# scan until next command (just before the `^`)
|
71
|
+
chars_skipped = @scanner.scan_until(/(?=\^)/)
|
72
|
+
|
73
|
+
if chars_skipped.nil?
|
74
|
+
# No more commands, return the rest
|
75
|
+
rest = @scanner.rest
|
76
|
+
@scanner.terminate
|
77
|
+
return rest
|
78
|
+
end
|
79
|
+
|
80
|
+
if chars_skipped.empty?
|
81
|
+
nil
|
82
|
+
else
|
83
|
+
chars_skipped
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require_relative '../reader'
|
2
|
+
|
3
|
+
module Zpl
|
4
|
+
module Transformer
|
5
|
+
end
|
6
|
+
|
7
|
+
# TODO: doc - explain how to make a transformer
|
8
|
+
class Transformer::Base
|
9
|
+
|
10
|
+
# TODO: doc
|
11
|
+
def apply(zpl_code, strip_spaces: true)
|
12
|
+
reader = Reader.new(zpl_code, strip_spaces: strip_spaces)
|
13
|
+
transformed_zpl = StringIO.new
|
14
|
+
|
15
|
+
while token = reader.next_token
|
16
|
+
if token.is_a?(Command)
|
17
|
+
# It's a command, transform it
|
18
|
+
if new_cmd = self.map_cmd(token)
|
19
|
+
transformed_zpl << new_cmd.to_zpl_string
|
20
|
+
end
|
21
|
+
else
|
22
|
+
# Not a command, just append it
|
23
|
+
transformed_zpl << token
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
transformed_zpl.string
|
28
|
+
end
|
29
|
+
|
30
|
+
# TODO: doc
|
31
|
+
def map_cmd(cmd)
|
32
|
+
cmd
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
# Returns an integer converted from the string *param*, or nil if it cannot be
|
38
|
+
# converted or when the string is empty.
|
39
|
+
def param_to_i?(param)
|
40
|
+
begin
|
41
|
+
Integer(param)
|
42
|
+
rescue ArgumentError # raised when *param* string cannot be converted to Integer
|
43
|
+
nil
|
44
|
+
rescue TypeError # raised when *param* is nil
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require_relative './base'
|
2
|
+
|
3
|
+
module Zpl
|
4
|
+
|
5
|
+
class Transformer::BaseScaler < Transformer::Base
|
6
|
+
|
7
|
+
def initialize(ratio)
|
8
|
+
@scale_ratio = ratio.to_f
|
9
|
+
end
|
10
|
+
|
11
|
+
protected
|
12
|
+
|
13
|
+
# Scale the given *number* by the configured ratio.
|
14
|
+
def scale_single_number(number)
|
15
|
+
(number * @scale_ratio).to_i
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require_relative '../font'
|
2
|
+
require_relative '../reader'
|
3
|
+
require_relative './base_scaler'
|
4
|
+
|
5
|
+
module Zpl::Transformer
|
6
|
+
|
7
|
+
# TODO: doc - Explain the algorithm used to scale bitmap font
|
8
|
+
#
|
9
|
+
# NOTE: will ONLY scale font commands
|
10
|
+
# NOTE: doesn't support partial font cmd (yet?)
|
11
|
+
# e.g: font with height but not width
|
12
|
+
class FontScaler < BaseScaler
|
13
|
+
# Supported font commands
|
14
|
+
#
|
15
|
+
# ^A - Scalable/Bitmapped Font
|
16
|
+
#
|
17
|
+
# Param -1: font name (value: [A-Z0-9])
|
18
|
+
# Note: This is part of the command name (second char), it will not appear in
|
19
|
+
# the command's params
|
20
|
+
# Param 0: field orientation (enum)
|
21
|
+
# Param 1: character height in dots
|
22
|
+
# Param 2: character width in dots
|
23
|
+
#
|
24
|
+
#
|
25
|
+
# ^CF - Change default Font
|
26
|
+
#
|
27
|
+
# Param 0: font name (value: [A-Z0-9])
|
28
|
+
# Param 1: character height in dots
|
29
|
+
# Param 2: character width in dots
|
30
|
+
|
31
|
+
def initialize(ratio, allow_font_change: true)
|
32
|
+
super(ratio)
|
33
|
+
@allow_font_change = allow_font_change
|
34
|
+
end
|
35
|
+
|
36
|
+
def map_cmd(raw_cmd)
|
37
|
+
font, cmd_kind = font_and_kind_from_cmd?(raw_cmd)
|
38
|
+
unless font
|
39
|
+
# Unable to extract the font from the command, it is probably not
|
40
|
+
# a font command, we don't touch it.
|
41
|
+
return raw_cmd
|
42
|
+
end
|
43
|
+
|
44
|
+
given_height, given_width = extract_given_sizes(raw_cmd, cmd_kind)
|
45
|
+
|
46
|
+
unless given_height && given_width
|
47
|
+
# Either param *height* or *width* is not given, this is not supported.
|
48
|
+
# Returning the same command.
|
49
|
+
return raw_cmd
|
50
|
+
end
|
51
|
+
|
52
|
+
font_cmd = FontCommand.new(raw_cmd, cmd_kind, font, given_height, given_width)
|
53
|
+
|
54
|
+
if font.scalable?
|
55
|
+
scale_scalable_font(font_cmd)
|
56
|
+
else
|
57
|
+
scale_bitmap_font(font_cmd)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
protected
|
62
|
+
|
63
|
+
# Helper data class with all the important data easily accessible for the conversion
|
64
|
+
FontCommand = Struct.new(:raw_cmd, :kind, :font, :height, :width)
|
65
|
+
|
66
|
+
def scale_scalable_font(font_cmd)
|
67
|
+
scaled_height = scale_single_number(font_cmd.height)
|
68
|
+
scaled_width = scale_single_number(font_cmd.width)
|
69
|
+
|
70
|
+
make_cmd(font_cmd.raw_cmd, font_cmd.kind, font_cmd.font, scaled_height, scaled_width)
|
71
|
+
end
|
72
|
+
|
73
|
+
def scale_bitmap_font(font_cmd)
|
74
|
+
given_font = font_cmd.font
|
75
|
+
if @allow_font_change
|
76
|
+
new_font = find_smallest_font_matching_size(given_font)
|
77
|
+
end
|
78
|
+
new_font ||= given_font
|
79
|
+
|
80
|
+
# Normalize
|
81
|
+
norm_height, _ = given_font.normalize_size(
|
82
|
+
height: font_cmd.height,
|
83
|
+
width: font_cmd.width,
|
84
|
+
)
|
85
|
+
|
86
|
+
scaled_height = scale_single_number(norm_height)
|
87
|
+
# Proportionnal computation of scaled_width from scaled_height
|
88
|
+
# based on proportion of new_font's base size
|
89
|
+
scaled_width = (new_font.base_width.to_f * scaled_height.to_f / new_font.base_height.to_f).to_i
|
90
|
+
|
91
|
+
# Normalize again
|
92
|
+
norm_scaled_height, norm_scaled_width = new_font.normalize_size(
|
93
|
+
height: scaled_height,
|
94
|
+
width: scaled_width,
|
95
|
+
)
|
96
|
+
|
97
|
+
make_cmd(font_cmd.raw_cmd, font_cmd.kind, new_font, norm_scaled_height, norm_scaled_width)
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def extract_given_sizes(cmd, cmd_kind)
|
103
|
+
# Format for ^CF: ^CFfont,height,width
|
104
|
+
# With `font`: the short font name [A-Z0-9]
|
105
|
+
# Format for ^A: ^A#orient,height,width
|
106
|
+
# With #: the short font name [A-Z0-9]
|
107
|
+
[param_to_i?(cmd.params[1]), param_to_i?(cmd.params[2])]
|
108
|
+
end
|
109
|
+
|
110
|
+
def find_smallest_font_matching_size(current_font)
|
111
|
+
Zpl::Font.all.each do |font|
|
112
|
+
next if current_font.base_size == font.base_size
|
113
|
+
next unless current_font.type == font.type
|
114
|
+
|
115
|
+
if (current_font.base_height % font.base_height) == 0 &&
|
116
|
+
(current_font.base_width % font.base_width) == 0
|
117
|
+
return font
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
nil
|
122
|
+
end
|
123
|
+
|
124
|
+
def font_and_kind_from_cmd?(raw_cmd)
|
125
|
+
if raw_cmd.name.start_with?('A')
|
126
|
+
font_name = raw_cmd.name[1]
|
127
|
+
cmd_kind = :set_font
|
128
|
+
elsif raw_cmd.name == "CF"
|
129
|
+
# Format: ^CFfont,height,width
|
130
|
+
font_name = raw_cmd.params[0]
|
131
|
+
cmd_kind = :change_default_font
|
132
|
+
else
|
133
|
+
return
|
134
|
+
end
|
135
|
+
[Zpl::Font.from_name?(font_name), cmd_kind]
|
136
|
+
end
|
137
|
+
|
138
|
+
def make_cmd(old_cmd, cmd_kind, new_font, height, width)
|
139
|
+
case cmd_kind
|
140
|
+
when :set_font
|
141
|
+
Zpl::Command.new "A#{ new_font.name }", [old_cmd.params[0], height, width]
|
142
|
+
when :change_default_font
|
143
|
+
Zpl::Command.new "CF", [new_font.name, height, width]
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require_relative './base_scaler'
|
2
|
+
|
3
|
+
module Zpl::Transformer
|
4
|
+
# TODO: doc
|
5
|
+
# It works by parsing ZPL commands, then edit the parameters of specific commands
|
6
|
+
# to scale the coordinates to the new dpi
|
7
|
+
class GenericScaler < BaseScaler
|
8
|
+
|
9
|
+
# TODO: doc - what is this hash for? format?
|
10
|
+
# TODO: rewrite?
|
11
|
+
COMMANDS_PARAM_INDEXES_TO_SCALE = {
|
12
|
+
# ^MN - Media Tracking
|
13
|
+
#
|
14
|
+
# Param 0: media being used
|
15
|
+
# Param 1: black mark offset in dots (optional)
|
16
|
+
"MN" => [1],
|
17
|
+
|
18
|
+
# ^BY - Bar Code Field Default
|
19
|
+
#
|
20
|
+
# Param 0: module width in dots
|
21
|
+
# Param 1: wide bar to narrow bar width ratio (float)
|
22
|
+
# Param 2: bar code height in dots
|
23
|
+
"BY" => [0, 2],
|
24
|
+
|
25
|
+
# ^FO - Field Origin
|
26
|
+
#
|
27
|
+
# Param 0: x-axis location in dots
|
28
|
+
# Param 1: y-axis location in dots
|
29
|
+
# Param 2: justification (enum)
|
30
|
+
"FO" => [0, 1],
|
31
|
+
|
32
|
+
# ^B2 - Interleaved 2 of 5 Bar Code
|
33
|
+
#
|
34
|
+
# Param 0: orientation (enum)
|
35
|
+
# Param 1: bar code height in dots
|
36
|
+
# Param 2: print interpretation line above code (bool)
|
37
|
+
"B2" => [1],
|
38
|
+
|
39
|
+
# ^GB - Graphic Box
|
40
|
+
#
|
41
|
+
# Param 0: box width in dots
|
42
|
+
# Param 1: box height in dots
|
43
|
+
# Param 2: border thickness
|
44
|
+
# Param 3: line color (enum)
|
45
|
+
# Param 4: degree of corner rounding (enum)
|
46
|
+
"GB" => [0, 1, 2],
|
47
|
+
|
48
|
+
# ^BC - Code 128 Bar Code (Subsets A, B, and C)
|
49
|
+
#
|
50
|
+
# Param 0: orientation (enum)
|
51
|
+
# Param 1: bar code height in dots
|
52
|
+
# Param 2: print interpretation line (bool)
|
53
|
+
# Param 3: print interpretation line above code (bool)
|
54
|
+
# Param 4: UCC check digit (bool)
|
55
|
+
# Param 5: mode (enum)
|
56
|
+
"BC" => [1],
|
57
|
+
}
|
58
|
+
|
59
|
+
def map_cmd(cmd)
|
60
|
+
return cmd unless cmd_need_scale? cmd
|
61
|
+
|
62
|
+
cmd_params = cmd.params
|
63
|
+
|
64
|
+
param_indexes_to_scale = COMMANDS_PARAM_INDEXES_TO_SCALE[cmd.name]
|
65
|
+
param_indexes_to_scale.each do |param_index|
|
66
|
+
if param_i = param_to_i?(cmd_params[param_index])
|
67
|
+
cmd_params[param_index] = scale_single_number(param_i)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
cmd
|
72
|
+
end
|
73
|
+
|
74
|
+
protected
|
75
|
+
|
76
|
+
def cmd_need_scale?(cmd)
|
77
|
+
!!COMMANDS_PARAM_INDEXES_TO_SCALE[cmd.name]
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require_relative './base'
|
2
|
+
|
3
|
+
module Zpl
|
4
|
+
class Transformer::Pipeline < Transformer::Base
|
5
|
+
|
6
|
+
def initialize(transformers)
|
7
|
+
@transformers = transformers
|
8
|
+
end
|
9
|
+
|
10
|
+
def map_cmd(cmd)
|
11
|
+
@transformers.each do |tr|
|
12
|
+
new_cmd = tr.map_cmd(cmd)
|
13
|
+
return unless new_cmd
|
14
|
+
cmd = new_cmd
|
15
|
+
end
|
16
|
+
cmd
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require_relative './zpl-transformer/version'
|
2
|
+
require_relative './zpl-transformer/reader'
|
3
|
+
require_relative './zpl-transformer/transformers'
|
4
|
+
|
5
|
+
module Zpl
|
6
|
+
|
7
|
+
# Returns the list of unique commands used in the given ZPL.
|
8
|
+
def self.uniq_commands(zpl_content)
|
9
|
+
uniq_cmds = Set.new
|
10
|
+
Reader.new(zpl_content).each_command do |cmd|
|
11
|
+
uniq_cmds << cmd.name
|
12
|
+
end
|
13
|
+
uniq_cmds.to_a
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "zpl-transformer/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "zpl-transformer"
|
8
|
+
spec.version = Zpl::VERSION
|
9
|
+
spec.authors = ["Benoit de Chezelles"]
|
10
|
+
spec.email = ["benoit.dechezelles@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Simple gem to scale ZPL label}
|
13
|
+
spec.homepage = "https://github.com/cheerz/zpl-transformer.rb"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
# Specify which files should be added to the gem when it is released.
|
17
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
18
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
19
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
20
|
+
end
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
24
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
25
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: zpl-transformer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Benoit de Chezelles
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-02-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
description:
|
56
|
+
email:
|
57
|
+
- benoit.dechezelles@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- ".rspec"
|
64
|
+
- ".travis.yml"
|
65
|
+
- Gemfile
|
66
|
+
- Gemfile.lock
|
67
|
+
- LICENSE.txt
|
68
|
+
- README.md
|
69
|
+
- Rakefile
|
70
|
+
- bin/console
|
71
|
+
- bin/setup
|
72
|
+
- lib/zpl-transformer.rb
|
73
|
+
- lib/zpl-transformer/command.rb
|
74
|
+
- lib/zpl-transformer/font.rb
|
75
|
+
- lib/zpl-transformer/reader.rb
|
76
|
+
- lib/zpl-transformer/transformer/base.rb
|
77
|
+
- lib/zpl-transformer/transformer/base_scaler.rb
|
78
|
+
- lib/zpl-transformer/transformer/font_scaler.rb
|
79
|
+
- lib/zpl-transformer/transformer/generic_scaler.rb
|
80
|
+
- lib/zpl-transformer/transformer/pipeline.rb
|
81
|
+
- lib/zpl-transformer/transformers.rb
|
82
|
+
- lib/zpl-transformer/version.rb
|
83
|
+
- zpl-transformer.gemspec
|
84
|
+
homepage: https://github.com/cheerz/zpl-transformer.rb
|
85
|
+
licenses:
|
86
|
+
- MIT
|
87
|
+
metadata: {}
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options: []
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
requirements: []
|
103
|
+
rubyforge_project:
|
104
|
+
rubygems_version: 2.5.1
|
105
|
+
signing_key:
|
106
|
+
specification_version: 4
|
107
|
+
summary: Simple gem to scale ZPL label
|
108
|
+
test_files: []
|