stationery 0.6.0 → 0.7.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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/README.md +72 -15
  4. data/lib/stationery/builder.rb +5 -3
  5. data/lib/stationery/canvas/text.rb +4 -3
  6. data/lib/stationery/canvas.rb +5 -2
  7. data/lib/stationery/document.rb +26 -9
  8. data/lib/stationery/elements.rb +1 -1
  9. data/lib/stationery/fonts/fallback.rb +3 -3
  10. data/lib/stationery/fonts/font.rb +39 -21
  11. data/lib/stationery/fonts/gsub/single_subst.rb +37 -0
  12. data/lib/stationery/fonts/gsub.rb +65 -20
  13. data/lib/stationery/fonts/ligatures.rb +6 -4
  14. data/lib/stationery/hyphenation/patterns/LICENSES.md +60 -0
  15. data/lib/stationery/hyphenation/patterns/de-1996.pat.txt +36709 -0
  16. data/lib/stationery/hyphenation/patterns/en-us.hyp.txt +14 -0
  17. data/lib/stationery/hyphenation/patterns/en-us.pat.txt +4938 -0
  18. data/lib/stationery/hyphenation/patterns/sv.pat.txt +4693 -0
  19. data/lib/stationery/hyphenation.rb +132 -0
  20. data/lib/stationery/images/png.rb +66 -0
  21. data/lib/stationery/images/resampled.rb +80 -0
  22. data/lib/stationery/layout/image.rb +28 -2
  23. data/lib/stationery/layout/paginator.rb +1 -1
  24. data/lib/stationery/layout/text.rb +2 -2
  25. data/lib/stationery/minitest.rb +5 -0
  26. data/lib/stationery/page_templates.rb +2 -1
  27. data/lib/stationery/pdf/assembler.rb +6 -2
  28. data/lib/stationery/pdf/page_labels.rb +71 -0
  29. data/lib/stationery/testing/inspector.rb +13 -0
  30. data/lib/stationery/testing/matchers.rb +10 -0
  31. data/lib/stationery/text/entities.rb +14 -4
  32. data/lib/stationery/text/paragraph.rb +1 -1
  33. data/lib/stationery/text/style.rb +14 -3
  34. data/lib/stationery/text/wrapper.rb +73 -12
  35. data/lib/stationery/version.rb +1 -1
  36. data/lib/stationery/warnings.rb +6 -0
  37. data/lib/stationery.rb +4 -0
  38. metadata +10 -1
@@ -2,51 +2,96 @@
2
2
 
3
3
  module Stationery
4
4
  module Fonts
5
- # Standard ligatures from the GSUB `liga` feature: LigatureSubst lookups
6
- # (format 1), directly or behind Extension lookups. Only `liga` applies;
7
- # contextual (`clig`) and discretionary (`dlig`) ligatures are left out.
8
- # Scripts, languages and lookup flags are not distinguished.
5
+ # Glyph substitution from the font's GSUB table: every feature's
6
+ # SingleSubst (type 1) and LigatureSubst (type 4) lookups, directly or
7
+ # behind Extension lookups. `liga` gives the standard ligatures; `smcp`,
8
+ # `onum`, `tnum`, `dlig`, `ss01` and the rest are applied on request.
9
+ # Contextual lookups (types 5, 6 and 8) are not read, so `calt` and
10
+ # `clig` do nothing. Scripts, languages and lookup flags are not
11
+ # distinguished.
9
12
  class Gsub
13
+ SINGLE = 1
10
14
  LIGATURE = 4
11
15
  EXTENSION = 7
16
+ LIGA = ["liga"].freeze
12
17
 
13
- # nil when the font has no GSUB table or no `liga` ligatures.
18
+ # nil when the font has no GSUB table, an unknown version, or no lookup
19
+ # of a supported type behind any feature.
14
20
  def self.parse(ttf)
15
21
  base = ttf.table_offset("GSUB")
16
22
  return unless base && ttf.u16(base) == 1
17
23
 
18
- lookups = Gpos.feature_lookups(ttf, base, "liga").map { |offset| lookup(ttf, offset) }.reject(&:empty?)
19
- new(lookups) if lookups.any?
24
+ lookups = {}
25
+ features = feature_indices(ttf, base + ttf.u16(base + 6)).transform_values do |indices|
26
+ indices.filter_map do |i|
27
+ subtables = lookups[i] ||= lookup(ttf, base + ttf.u16(base + 8), i)
28
+ [i, subtables] unless subtables.empty?
29
+ end.freeze
30
+ end
31
+ features.reject! { |_, lookups_for| lookups_for.empty? }
32
+ new(features) if features.any?
20
33
  end
21
34
 
22
- # A lookup's LigatureSubst subtables; other lookup types have none.
23
- def self.lookup(ttf, offset)
35
+ # { tag => [lookup index, ...] } from the FeatureList, every record of a
36
+ # tag merged, in LookupList order.
37
+ def self.feature_indices(ttf, list)
38
+ Array.new(ttf.u16(list)) { |i| list + 2 + (i * 6) }.each_with_object({}) do |record, features|
39
+ tag = ttf.data.byteslice(record, 4)
40
+ feature = list + ttf.u16(record + 4)
41
+ indices = Array.new(ttf.u16(feature + 2)) { |j| ttf.u16(feature + 4 + (j * 2)) }
42
+ features[tag] = ((features[tag] || []) | indices).sort
43
+ end
44
+ end
45
+ private_class_method :feature_indices
46
+
47
+ # The `index`th lookup's supported subtables; other types have none.
48
+ def self.lookup(ttf, list, index)
49
+ offset = list + ttf.u16(list + 2 + (index * 2))
24
50
  type = ttf.u16(offset)
25
51
  subtables = Array.new(ttf.u16(offset + 4)) { |i| offset + ttf.u16(offset + 6 + (i * 2)) }
26
52
  subtables.filter_map do |subtable|
27
- if type == LIGATURE
28
- LigatureSubst.read(ttf, subtable)
29
- elsif type == EXTENSION && ttf.u16(subtable + 2) == LIGATURE
30
- LigatureSubst.read(ttf, subtable + ttf.u32(subtable + 4))
53
+ if type == EXTENSION
54
+ read(ttf.u16(subtable + 2), ttf, subtable + ttf.u32(subtable + 4))
55
+ else
56
+ read(type, ttf, subtable)
31
57
  end
32
58
  end.freeze
33
59
  end
34
60
  private_class_method :lookup
35
61
 
36
- def initialize(lookups)
37
- @lookups = lookups.freeze
62
+ def self.read(type, ttf, offset)
63
+ case type
64
+ when SINGLE then SingleSubst.read(ttf, offset)
65
+ when LIGATURE then LigatureSubst.read(ttf, offset)
66
+ end
67
+ end
68
+ private_class_method :read
69
+
70
+ def initialize(features)
71
+ @features = features.freeze
72
+ @plans = {}
38
73
  freeze
39
74
  end
40
75
 
41
- # [[gid, number of source glyphs it stands for], ...]. Lookups apply in
42
- # order; within one, the first subtable matching at a position wins and
43
- # its glyph is not matched again by that lookup.
44
- def substitute(gids)
45
- @lookups.reduce(gids.map { |gid| [gid, 1] }) { |glyphs, subtables| apply(subtables, glyphs) }
76
+ # The feature tags this font can apply, e.g. ["liga", "onum", "tnum"].
77
+ def features = @features.keys
78
+
79
+ # [[gid, number of source glyphs it stands for], ...] after applying the
80
+ # lookups of `tags` (feature tag Strings) in LookupList order, each once.
81
+ # Within a lookup the first subtable matching at a position wins and its
82
+ # glyph is not matched again by that lookup.
83
+ def substitute(gids, tags = LIGA)
84
+ plan(tags).reduce(gids.map { |gid| [gid, 1] }) { |glyphs, subtables| apply(subtables, glyphs) }
46
85
  end
47
86
 
48
87
  private
49
88
 
89
+ # The lookups the tags name, merged and ordered; remembered per tag list.
90
+ def plan(tags)
91
+ @plans[tags] ||= tags.flat_map { |tag| @features[tag] || [] }.uniq(&:first).sort_by(&:first)
92
+ .map(&:last).freeze
93
+ end
94
+
50
95
  def apply(subtables, glyphs)
51
96
  ids = glyphs.map(&:first)
52
97
  result = []
@@ -2,12 +2,14 @@
2
2
 
3
3
  module Stationery
4
4
  module Fonts
5
- # Chooses where a font's ligatures come from. Every source answers
6
- # `substitute(gids)` with [[gid, source glyph count], ...].
5
+ # Chooses where a font's glyph substitutions come from. Every source
6
+ # answers `substitute(gids, feature_tags)` with [[gid, source glyph
7
+ # count], ...] and `features` with the tags it can apply.
7
8
  module Ligatures
8
- # For fonts without ligatures: every glyph stands for itself.
9
+ # For fonts without a usable GSUB table: every glyph stands for itself.
9
10
  module NONE
10
- def self.substitute(gids) = gids.map { |gid| [gid, 1] }
11
+ def self.substitute(gids, _tags = Gsub::LIGA) = gids.map { |gid| [gid, 1] }
12
+ def self.features = []
11
13
  end
12
14
 
13
15
  def self.for(ttf) = Gsub.parse(ttf) || NONE
@@ -0,0 +1,60 @@
1
+ # Hyphenation patterns
2
+
3
+ The `*.pat.txt` and `*.hyp.txt` files in this directory are the plain-text
4
+ (UTF-8) pattern and exception lists from the TeX `hyph-utf8` package
5
+ (https://github.com/hyphenation/tex-hyphen, `hyph-utf8/tex/generic/hyph-utf8/patterns/txt/`),
6
+ unmodified. Each file's own copyright and licence follow, copied from the header
7
+ of its `.tex` source in the same package. The Ruby code that reads them
8
+ (`lib/stationery/hyphenation.rb`) is stationery's and under stationery's licence.
9
+
10
+ ## en-us.pat.txt, en-us.hyp.txt — Hyphenation patterns for American English
11
+
12
+ Copyright (C) 1990, 2004, 2005 Gerard D.C. Kuiken. Source: `hyph-en-us.tex`
13
+ (`ushyphmax.tex`, version 2005-05-30). Based on Liang's patterns and the
14
+ Hyphenation Exception Log published in TUGboat, Volume 10 (1989), No. 3.
15
+
16
+ > Copying and distribution of this file, with or without modification, are
17
+ > permitted in any medium without royalty provided the copyright notice and
18
+ > this notice are preserved.
19
+
20
+ Typesetting minimums: 2 letters before a break, 3 after.
21
+
22
+ ## de-1996.pat.txt — German Hyphenation Patterns (Reformed Orthography, 2006)
23
+
24
+ TeX-Trennmuster für die reformierte (2006) deutsche Rechtschreibung, version
25
+ 2024-02-28, by the Deutschsprachige Trennmustermannschaft (trennmuster@dante.de).
26
+ Copyright (c) 2013-2024 Stephan Hennig, Werner Lemberg, Günter Milde, Sander van
27
+ Geloven, Georg Pfeiffer, Gisbert W. Selke, Tobias Wendorf, Keno Wehr. Source:
28
+ `hyph-de-1996.tex`; generated from
29
+ https://repo.or.cz/w/wortliste.git?a=commit;h=304aaa2188a75e57afe36bad0443d90a9c715b8e.
30
+ Licence: MIT (https://opensource.org/licenses/mit-license.php).
31
+
32
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
33
+ > of this software and associated documentation files (the “Software”), to deal
34
+ > in the Software without restriction, including without limitation the rights
35
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
36
+ > copies of the Software, and to permit persons to whom the Software is
37
+ > furnished to do so, subject to the following conditions:
38
+ >
39
+ > The above copyright notice and this permission notice shall be included in
40
+ > all copies or substantial portions of the Software.
41
+ >
42
+ > THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
43
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
44
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
45
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
46
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
47
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
48
+ > SOFTWARE.
49
+
50
+ Typesetting minimums: 2 letters before a break, 2 after.
51
+
52
+ ## sv.pat.txt — Hyphenation patterns for Swedish
53
+
54
+ Copyright (C) 1994 Jan Michael Rynning (jmr (at) incolumitas.se), version
55
+ 1994-03-03. Source: `hyph-sv.tex`. Licence: LaTeX Project Public License,
56
+ version 1.2 or later (https://www.latex-project.org/lppl/). This file is
57
+ distributed unmodified, as the "Current Maintainer" released it; the LPPL
58
+ requires that a modified version be renamed and identified as such.
59
+
60
+ Typesetting minimums: 2 letters before a break, 2 after.