arrolio 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 +7 -0
- data/.rspec +2 -0
- data/.rubocop.yml +343 -0
- data/CHANGELOG.md +18 -0
- data/CLAUDE.md +154 -0
- data/Gemfile +15 -0
- data/LICENSE +24 -0
- data/README.adoc +95 -0
- data/Rakefile +21 -0
- data/TODO.complete/01-true-type-font-metrics.md +56 -0
- data/TODO.complete/02-font-embedding.md +51 -0
- data/TODO.complete/03-knuth-plass-breaker.md +42 -0
- data/TODO.complete/04-table-flowable.md +60 -0
- data/TODO.complete/05-list-flowable.md +50 -0
- data/TODO.complete/06-notes-and-examples.md +41 -0
- data/TODO.complete/07-odd-even-templates-and-page-numbering.md +63 -0
- data/TODO.complete/08-image-flowable.md +45 -0
- data/TODO.complete/09-toc-and-leaders.md +66 -0
- data/TODO.complete/10-hyperlinks-and-destinations.md +46 -0
- data/TODO.complete/11-pdf-outline-bookmarks.md +47 -0
- data/TODO.complete/12-metadata-info-dict.md +42 -0
- data/TODO.complete/13-xmp-metadata.md +43 -0
- data/TODO.complete/14-footnotes.md +43 -0
- data/TODO.complete/15-color-management.md +33 -0
- data/TODO.complete/16-pdfua-struct-tree.md +35 -0
- data/TODO.complete/17-alt-text-and-actual-text.md +28 -0
- data/TODO.complete/18-math-rendering.md +41 -0
- data/TODO.complete/19-preformatted-and-code.md +31 -0
- data/TODO.complete/20-page-break-controls.md +46 -0
- data/TODO.complete/21-svg-renderer.md +40 -0
- data/TODO.complete/22-definition-lists.md +36 -0
- data/TODO.complete/23-multi-column.md +34 -0
- data/TODO.complete/24-cover-page-fidelity.md +40 -0
- data/TODO.complete/25-i18n-and-multi-language.md +28 -0
- data/TODO.complete/26-pixel-diff-harness.md +36 -0
- data/TODO.complete/27-pdfrb-only-text-extraction.md +37 -0
- data/TODO.complete/28-restore-nested-module-style.md +41 -0
- data/TODO.complete/29-font-embedding-wiring-debug.md +72 -0
- data/TODO.complete/30-adapter-decomposition.md +76 -0
- data/TODO.complete/31-subscript-superscript-inline-runs.md +56 -0
- data/TODO.complete/32-debug-output-cleanup.md +59 -0
- data/TODO.complete/33-comprehensive-adapter-specs.md +55 -0
- data/TODO.complete/34-table-column-auto-layout.md +50 -0
- data/TODO.complete/35-image-path-resolution.md +66 -0
- data/TODO.complete/36-xsl-converter-completeness.md +40 -0
- data/TODO.complete/37-content-model-completeness.md +54 -0
- data/TODO.complete/38-pipeline-config-externalization.md +55 -0
- data/TODO.complete/39-two-pass-cross-reference-resolution.md +66 -0
- data/TODO.complete/40-vertical-writing-modes.md +63 -0
- data/TODO.complete/41-pdf-annotations-hyperlinks.md +53 -0
- data/TODO.complete/42-font-manifest-configuration.md +75 -0
- data/TODO.complete/43-shrink-to-fit-containers.md +52 -0
- data/TODO.complete/44-back-of-book-index-generation.md +51 -0
- data/TODO.complete/45-pdf-forms-acroform.md +52 -0
- data/TODO.complete/46-pdf-digital-signatures.md +47 -0
- data/TODO.complete/47-ocp-flavor-separation.md +50 -0
- data/TODO.complete/48-oiml-as-pure-configuration.md +80 -0
- data/TODO.complete/49-oiml-flowbuilder-as-configuration.md +39 -0
- data/TODO.complete/50-delete-oiml-ruby-code.md +53 -0
- data/TODO.complete/51-xsl-as-authoritative-source.md +68 -0
- data/TODO.complete/52-flavor-packaging-separation.md +49 -0
- data/TODO.complete/53-second-flavor-spike.md +74 -0
- data/TODO.complete/54-flavor-manifest-contract.md +72 -0
- data/TODO.complete/55-header-footer-style-configuration.md +57 -0
- data/TODO.complete/56-generic-adapter-mece-decomposition.md +56 -0
- data/TODO.complete/57-strict-section-typing.md +49 -0
- data/TODO.complete/58-flavor-packaging-gem-convention.md +47 -0
- data/TODO.complete/59-xsl-converter-profiles.md +76 -0
- data/TODO.complete/60-adapter-fidelity-footnotes-notes-examples.md +62 -0
- data/TODO.complete/61-error-model-and-strict-mode.md +62 -0
- data/TODO.complete/62-font-resolution-and-strict-fonts.md +66 -0
- data/TODO.complete/63-purge-hardcoded-vocabulary-from-generic-core.md +68 -0
- data/TODO.complete/64-selector-driven-adapter-specs.md +46 -0
- data/TODO.complete/66-semantic-content-types.md +171 -0
- data/TODO.complete/README.md +48 -0
- data/TODO.implementation/01-foundation.md +46 -0
- data/TODO.implementation/02-content-contract.md +47 -0
- data/TODO.implementation/03-layoutspec-contract.md +46 -0
- data/TODO.implementation/04-style-system.md +43 -0
- data/TODO.implementation/05-visitor-pattern.md +40 -0
- data/TODO.implementation/06-afm-data-and-parser.md +37 -0
- data/TODO.implementation/07-afm-metrics.md +41 -0
- data/TODO.implementation/08-truetype-tables-and-metrics.md +48 -0
- data/TODO.implementation/09-font-registry-and-glyph-measurer.md +49 -0
- data/TODO.implementation/10-break-opportunity-and-inline-run.md +47 -0
- data/TODO.implementation/11-greedy-line-breaker.md +48 -0
- data/TODO.implementation/12-knuth-plass-line-breaker.md +46 -0
- data/TODO.implementation/13-line-and-alignment.md +45 -0
- data/TODO.implementation/14-knuth-elements-and-breaker.md +56 -0
- data/TODO.implementation/15-refactor-text-layout-on-knuth.md +47 -0
- data/TODO.implementation/16-flowable-and-frame.md +52 -0
- data/TODO.implementation/17-flow-context.md +50 -0
- data/TODO.implementation/18-text-flowable.md +52 -0
- data/TODO.implementation/19-spacer-and-page-break.md +52 -0
- data/TODO.implementation/20-engine-paged.md +63 -0
- data/TODO.implementation/21-output-tree.md +56 -0
- data/TODO.implementation/22-pdf-renderer.md +77 -0
- data/TODO.implementation/23-end-to-end-smoke.md +53 -0
- data/TODO.implementation/24-page-template-and-region.md +51 -0
- data/TODO.implementation/25-page-sequence-master.md +54 -0
- data/TODO.implementation/26-two-pass-layout.md +66 -0
- data/TODO.implementation/27-table-model.md +48 -0
- data/TODO.implementation/28-table-layouts.md +53 -0
- data/TODO.implementation/29-table-renderer.md +55 -0
- data/TODO.implementation/30-table-flowable.md +53 -0
- data/TODO.implementation/31-list-model.md +45 -0
- data/TODO.implementation/32-list-flowable.md +54 -0
- data/TODO.implementation/33-image-flowable.md +59 -0
- data/TODO.implementation/34-svg-renderer.md +58 -0
- data/TODO.implementation/35-svg-as-form-xobject.md +38 -0
- data/TODO.implementation/36-inline-builder.md +50 -0
- data/TODO.implementation/37-hyperlink.md +53 -0
- data/TODO.implementation/38-sub-superscript.md +44 -0
- data/TODO.implementation/39-leader-and-field-runs.md +60 -0
- data/TODO.implementation/40-bookmark-outline.md +45 -0
- data/TODO.implementation/41-destinations.md +52 -0
- data/TODO.implementation/42-toc-builder.md +52 -0
- data/TODO.implementation/43-footnotes.md +51 -0
- data/TODO.implementation/44-multi-column.md +45 -0
- data/TODO.implementation/45-page-numbering-and-section-tracking.md +50 -0
- data/TODO.implementation/46-color-management.md +47 -0
- data/TODO.implementation/47-metadata-and-xmp.md +41 -0
- data/TODO.implementation/48-struct-tree-and-marked-content.md +49 -0
- data/TODO.implementation/49-alt-text-and-actual-text.md +43 -0
- data/TODO.implementation/50-composer-facade.md +59 -0
- data/TODO.implementation/51-style-presets.md +44 -0
- data/TODO.implementation/52-oiml-adapter-and-e2e-pipeline.md +120 -0
- data/TODO.implementation/53-oiml-layout-spec.md +121 -0
- data/TODO.implementation/54-oiml-pipeline.md +97 -0
- data/TODO.implementation/55-pdf-comparator.md +98 -0
- data/TODO.implementation/56-diff-formatters.md +95 -0
- data/TODO.implementation/57-path-locator-and-enrichment.md +74 -0
- data/TODO.implementation/58-rspec-matchers-and-harness-api.md +124 -0
- data/TODO.implementation/59-pixel-diff-mode.md +93 -0
- data/TODO.implementation/60-yard-documentation.md +43 -0
- data/TODO.implementation/61-benchmarks-and-stress-tests.md +50 -0
- data/TODO.implementation/62-validation-framework.md +107 -0
- data/TODO.implementation/63-layout-rules-profile.md +110 -0
- data/TODO.implementation/64-pdfa-and-pdfua-conformance.md +74 -0
- data/TODO.implementation/65-feature-extractor.md +64 -0
- data/TODO.implementation/66-auto-fixer.md +75 -0
- data/TODO.implementation/README.md +169 -0
- data/data/arrolio/afm/Courier-Bold.afm +342 -0
- data/data/arrolio/afm/Courier-BoldOblique.afm +342 -0
- data/data/arrolio/afm/Courier-Oblique.afm +342 -0
- data/data/arrolio/afm/Courier.afm +342 -0
- data/data/arrolio/afm/Helvetica-Bold.afm +2827 -0
- data/data/arrolio/afm/Helvetica-BoldOblique.afm +2827 -0
- data/data/arrolio/afm/Helvetica-Oblique.afm +3051 -0
- data/data/arrolio/afm/Helvetica.afm +3051 -0
- data/data/arrolio/afm/Symbol.afm +213 -0
- data/data/arrolio/afm/Times-Bold.afm +2588 -0
- data/data/arrolio/afm/Times-BoldItalic.afm +2384 -0
- data/data/arrolio/afm/Times-Italic.afm +2667 -0
- data/data/arrolio/afm/Times-Roman.afm +2419 -0
- data/data/arrolio/afm/ZapfDingbats.afm +225 -0
- data/data/arrolio/glyphlist.txt +4306 -0
- data/exe/arrolio2pdf +28 -0
- data/lib/arrolio/asset_resolver.rb +36 -0
- data/lib/arrolio/color.rb +107 -0
- data/lib/arrolio/column_set.rb +64 -0
- data/lib/arrolio/composer.rb +10 -0
- data/lib/arrolio/config_driven_pipeline.rb +144 -0
- data/lib/arrolio/content/bibliography_item.rb +42 -0
- data/lib/arrolio/content/builder.rb +108 -0
- data/lib/arrolio/content/document.rb +60 -0
- data/lib/arrolio/content/example.rb +43 -0
- data/lib/arrolio/content/figure_group.rb +39 -0
- data/lib/arrolio/content/footnote.rb +46 -0
- data/lib/arrolio/content/form_field.rb +47 -0
- data/lib/arrolio/content/formula.rb +34 -0
- data/lib/arrolio/content/heading.rb +42 -0
- data/lib/arrolio/content/hyperlink.rb +42 -0
- data/lib/arrolio/content/image.rb +36 -0
- data/lib/arrolio/content/index_entry.rb +52 -0
- data/lib/arrolio/content/inline_run.rb +51 -0
- data/lib/arrolio/content/list.rb +60 -0
- data/lib/arrolio/content/note.rb +48 -0
- data/lib/arrolio/content/page_break.rb +28 -0
- data/lib/arrolio/content/paragraph.rb +38 -0
- data/lib/arrolio/content/preformatted.rb +47 -0
- data/lib/arrolio/content/section.rb +39 -0
- data/lib/arrolio/content/table.rb +117 -0
- data/lib/arrolio/content/term_entry.rb +43 -0
- data/lib/arrolio/content.rb +27 -0
- data/lib/arrolio/data_dir.rb +28 -0
- data/lib/arrolio/engine/cross_reference_registry.rb +69 -0
- data/lib/arrolio/engine/paged.rb +186 -0
- data/lib/arrolio/engine.rb +9 -0
- data/lib/arrolio/error.rb +70 -0
- data/lib/arrolio/flavor/manifest.rb +111 -0
- data/lib/arrolio/flavor/registry.rb +54 -0
- data/lib/arrolio/flavor.rb +23 -0
- data/lib/arrolio/flow_context.rb +37 -0
- data/lib/arrolio/flowable.rb +66 -0
- data/lib/arrolio/flowables/footnote_marker_flowable.rb +28 -0
- data/lib/arrolio/flowables/heading_flowable.rb +23 -0
- data/lib/arrolio/flowables/image_flowable.rb +53 -0
- data/lib/arrolio/flowables/list_flowable.rb +117 -0
- data/lib/arrolio/flowables/note_flowable.rb +19 -0
- data/lib/arrolio/flowables/page_break.rb +15 -0
- data/lib/arrolio/flowables/page_sequence_start.rb +28 -0
- data/lib/arrolio/flowables/spacer.rb +34 -0
- data/lib/arrolio/flowables/table_flowable.rb +147 -0
- data/lib/arrolio/flowables/text_flowable.rb +95 -0
- data/lib/arrolio/flowables/toc_line_flowable.rb +35 -0
- data/lib/arrolio/flowables/two_column_block.rb +56 -0
- data/lib/arrolio/flowables.rb +19 -0
- data/lib/arrolio/font/embedder.rb +197 -0
- data/lib/arrolio/font/manifest.rb +114 -0
- data/lib/arrolio/font/resolver.rb +161 -0
- data/lib/arrolio/font/text_encoder.rb +28 -0
- data/lib/arrolio/font.rb +10 -0
- data/lib/arrolio/font_metrics/afm_metrics.rb +91 -0
- data/lib/arrolio/font_metrics/registry.rb +55 -0
- data/lib/arrolio/font_metrics/true_type_metrics.rb +95 -0
- data/lib/arrolio/font_metrics.rb +10 -0
- data/lib/arrolio/frame.rb +36 -0
- data/lib/arrolio/generic_adapter.rb +672 -0
- data/lib/arrolio/generic_flow_builder.rb +338 -0
- data/lib/arrolio/glyph_measurer.rb +71 -0
- data/lib/arrolio/harness/pdf_diff.rb +104 -0
- data/lib/arrolio/harness/pixel_diff.rb +122 -0
- data/lib/arrolio/harness/text_diff.rb +53 -0
- data/lib/arrolio/harness/text_extractor.rb +58 -0
- data/lib/arrolio/harness.rb +10 -0
- data/lib/arrolio/inline_run.rb +59 -0
- data/lib/arrolio/layout_spec/flow.rb +17 -0
- data/lib/arrolio/layout_spec/loader.rb +117 -0
- data/lib/arrolio/layout_spec/page_template.rb +127 -0
- data/lib/arrolio/layout_spec/page_template_selector.rb +58 -0
- data/lib/arrolio/layout_spec/region.rb +38 -0
- data/lib/arrolio/layout_spec/scale_length.rb +34 -0
- data/lib/arrolio/layout_spec.rb +90 -0
- data/lib/arrolio/logger.rb +54 -0
- data/lib/arrolio/output/page.rb +69 -0
- data/lib/arrolio/output/placed_box.rb +68 -0
- data/lib/arrolio/output/region.rb +32 -0
- data/lib/arrolio/output.rb +10 -0
- data/lib/arrolio/renderer/accessibility_tagger.rb +70 -0
- data/lib/arrolio/renderer/font_registry.rb +32 -0
- data/lib/arrolio/renderer/link_annotator.rb +62 -0
- data/lib/arrolio/renderer/outline_builder.rb +102 -0
- data/lib/arrolio/renderer/pdf.rb +493 -0
- data/lib/arrolio/renderer/signature_config.rb +59 -0
- data/lib/arrolio/renderer/structure_tree_builder.rb +117 -0
- data/lib/arrolio/renderer/xmp_builder.rb +92 -0
- data/lib/arrolio/renderer.rb +17 -0
- data/lib/arrolio/style/definition.rb +98 -0
- data/lib/arrolio/style/loader.rb +25 -0
- data/lib/arrolio/style/registry.rb +53 -0
- data/lib/arrolio/style.rb +9 -0
- data/lib/arrolio/table/auto_layout.rb +91 -0
- data/lib/arrolio/table.rb +9 -0
- data/lib/arrolio/text_direction.rb +81 -0
- data/lib/arrolio/text_layout/break_opportunity.rb +103 -0
- data/lib/arrolio/text_layout/greedy.rb +110 -0
- data/lib/arrolio/text_layout/knuth_plass/breaker.rb +265 -0
- data/lib/arrolio/text_layout/knuth_plass/item.rb +84 -0
- data/lib/arrolio/text_layout/knuth_plass/item_builder.rb +103 -0
- data/lib/arrolio/text_layout/knuth_plass.rb +12 -0
- data/lib/arrolio/text_layout/line.rb +72 -0
- data/lib/arrolio/text_layout.rb +11 -0
- data/lib/arrolio/toc_builder.rb +31 -0
- data/lib/arrolio/version.rb +5 -0
- data/lib/arrolio/writing_mode.rb +75 -0
- data/lib/arrolio.rb +42 -0
- data/script/smoke.rb +32 -0
- data/scripts/write_content_extras.py +227 -0
- data/scripts/xsl_profiles/standoc.yml +218 -0
- data/scripts/xsl_to_config.rb +391 -0
- metadata +394 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Arrolio
|
|
4
|
+
module Font
|
|
5
|
+
# Converts a Unicode string into a 2-byte-big-endian
|
|
6
|
+
# glyph-ID sequence for an Identity-H-encoded CIDFont.
|
|
7
|
+
class TextEncoder
|
|
8
|
+
attr_reader :embedder
|
|
9
|
+
|
|
10
|
+
def initialize(embedder)
|
|
11
|
+
@embedder = embedder
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Returns a binary-encoded String of 2-byte GIDs for the
|
|
15
|
+
# codepoints in +text+. The renderer passes this to
|
|
16
|
+
# +canvas.text+ instead of the Unicode string.
|
|
17
|
+
def encode(text)
|
|
18
|
+
bytes = String.new.force_encoding(Encoding::BINARY)
|
|
19
|
+
text.each_codepoint do |cp|
|
|
20
|
+
gid = @embedder.subset_gid_for_codepoint(cp)
|
|
21
|
+
bytes << (gid >> 8).chr(Encoding::BINARY)
|
|
22
|
+
bytes << (gid & 0xff).chr(Encoding::BINARY)
|
|
23
|
+
end
|
|
24
|
+
bytes
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
data/lib/arrolio/font.rb
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Arrolio
|
|
4
|
+
module Font
|
|
5
|
+
autoload :TextEncoder, 'arrolio/font/text_encoder'
|
|
6
|
+
autoload :Embedder, 'arrolio/font/embedder'
|
|
7
|
+
autoload :Manifest, 'arrolio/font/manifest'
|
|
8
|
+
autoload :Resolver, 'arrolio/font/resolver'
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Arrolio
|
|
4
|
+
module FontMetrics
|
|
5
|
+
# Glyph-width lookup against Pdfrb's AFM data for one of the 14
|
|
6
|
+
# PDF standard Type1 fonts.
|
|
7
|
+
class AfmMetrics
|
|
8
|
+
UNITS_PER_EM = 1000
|
|
9
|
+
|
|
10
|
+
attr_reader :font_name, :afm
|
|
11
|
+
|
|
12
|
+
def initialize(font_name, afm)
|
|
13
|
+
@font_name = font_name.to_s
|
|
14
|
+
@afm = afm
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.for_name(name)
|
|
18
|
+
afm = Pdfrb::Font::Type1::FontMetrics.for_standard_font(name.to_s)
|
|
19
|
+
return nil unless afm
|
|
20
|
+
|
|
21
|
+
new(name, afm)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def units_per_em
|
|
25
|
+
UNITS_PER_EM
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def advance_width(char)
|
|
29
|
+
metric = metric_for(char)
|
|
30
|
+
metric ? metric[:width] : 0
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def width_of_string(str, font_size:)
|
|
34
|
+
scale = font_size.to_f / units_per_em
|
|
35
|
+
str.each_char.sum { |c| advance_width(c) * scale }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def ascender(font_size: 12)
|
|
39
|
+
(afm.ascender || 800) * font_size.to_f / units_per_em
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def descender(font_size: 12)
|
|
43
|
+
(afm.descender || -200) * font_size.to_f / units_per_em
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def cap_height(font_size: 12)
|
|
47
|
+
(afm.cap_height || 700) * font_size.to_f / units_per_em
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def line_height(font_size:, line_spacing: 1.2)
|
|
51
|
+
((afm.ascender || 800).to_f - (afm.descender || -200).to_f) *
|
|
52
|
+
font_size.to_f / units_per_em * line_spacing.to_f
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def metric_for(char)
|
|
58
|
+
codepoint = char.ord
|
|
59
|
+
if codepoint < 0x80
|
|
60
|
+
m = afm.metrics.char_metrics[codepoint]
|
|
61
|
+
return m if m
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
name = glyph_name_for(char)
|
|
65
|
+
afm.metrics.char_metrics[name] if name
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def glyph_name_for(char)
|
|
69
|
+
@glyph_name_cache ||= {}
|
|
70
|
+
return @glyph_name_cache[char] if @glyph_name_cache.key?(char)
|
|
71
|
+
|
|
72
|
+
@glyph_name_cache[char] = unicode_to_name[char.ord]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def unicode_to_name
|
|
76
|
+
return @unicode_to_name if @unicode_to_name
|
|
77
|
+
|
|
78
|
+
table = Pdfrb::Font::GlyphList.table
|
|
79
|
+
@unicode_to_name = {}
|
|
80
|
+
table.each do |gname, str|
|
|
81
|
+
cp = str.ord
|
|
82
|
+
@unicode_to_name[cp] ||= gname if cp
|
|
83
|
+
end
|
|
84
|
+
(0x21..0x7E).each do |cp|
|
|
85
|
+
@unicode_to_name[cp] ||= cp.chr
|
|
86
|
+
end
|
|
87
|
+
@unicode_to_name
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Arrolio
|
|
4
|
+
module FontMetrics
|
|
5
|
+
module Registry
|
|
6
|
+
@cache = {}
|
|
7
|
+
@ttf_paths = {}
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
attr_reader :ttf_paths
|
|
11
|
+
|
|
12
|
+
def register_ttf(font_name, path)
|
|
13
|
+
@ttf_paths[font_name.to_s] = path
|
|
14
|
+
@cache.delete(font_name.to_s)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def register_ttf_all(paths, strict: false)
|
|
18
|
+
missing = []
|
|
19
|
+
paths.each do |name, path|
|
|
20
|
+
register_ttf(name, path)
|
|
21
|
+
missing << [name, path] if strict && path && File.exist?(path) == false
|
|
22
|
+
end
|
|
23
|
+
return unless strict && missing.any?
|
|
24
|
+
|
|
25
|
+
raise Arrolio::RenderError.new(
|
|
26
|
+
'strict mode: missing required font files: ' +
|
|
27
|
+
missing.map { |name, path| "#{name}=#{path}" }.join(', '),
|
|
28
|
+
missing_fonts: missing
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def [](font_name)
|
|
33
|
+
key = font_name.to_s
|
|
34
|
+
return @cache[key] if @cache.key?(key)
|
|
35
|
+
|
|
36
|
+
if @ttf_paths.key?(key) && File.exist?(@ttf_paths[key])
|
|
37
|
+
@cache[key] = TrueTypeMetrics.from_file(key, @ttf_paths[key])
|
|
38
|
+
return @cache[key]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
@cache[key] = AfmMetrics.for_name(key)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def reset!
|
|
45
|
+
@cache.clear
|
|
46
|
+
@ttf_paths.clear
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def known?(font_name)
|
|
50
|
+
!self[font_name].nil?
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fontisan'
|
|
4
|
+
|
|
5
|
+
module Arrolio
|
|
6
|
+
module FontMetrics
|
|
7
|
+
# Glyph-width lookup from a TrueType/OpenType font file.
|
|
8
|
+
# Reads head, hhea, hmtx, cmap, OS/2 tables via Fontisan.
|
|
9
|
+
# Same interface as AfmMetrics so callers can swap freely.
|
|
10
|
+
class TrueTypeMetrics
|
|
11
|
+
attr_reader :font_name, :fontisan_font
|
|
12
|
+
|
|
13
|
+
def initialize(font_name, fontisan_font)
|
|
14
|
+
@font_name = font_name.to_s
|
|
15
|
+
@fontisan_font = fontisan_font
|
|
16
|
+
@cmap = fontisan_font.table('cmap')
|
|
17
|
+
@unicode_to_gid = @cmap ? @cmap.unicode_mappings.transform_keys(&:to_i) : {}
|
|
18
|
+
@head = fontisan_font.table('head')
|
|
19
|
+
@hhea = fontisan_font.table('hhea')
|
|
20
|
+
@os2 = fontisan_font.table('OS/2')
|
|
21
|
+
@hmtx = fontisan_font.table('hmtx')
|
|
22
|
+
parse_hmtx
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def parse_hmtx
|
|
26
|
+
return unless @hmtx && @hhea
|
|
27
|
+
|
|
28
|
+
maxp = @fontisan_font.table('maxp')
|
|
29
|
+
num_metrics = @hhea.number_of_h_metrics rescue nil
|
|
30
|
+
num_glyphs = maxp&.num_glyphs
|
|
31
|
+
return unless num_metrics && num_glyphs
|
|
32
|
+
|
|
33
|
+
@hmtx.parse_with_context(num_metrics, num_glyphs)
|
|
34
|
+
rescue StandardError
|
|
35
|
+
nil
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.from_file(font_name, path)
|
|
39
|
+
font = Fontisan::FontLoader.load(path)
|
|
40
|
+
new(font_name, font)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def units_per_em
|
|
44
|
+
@head ? @head.units_per_em : 1000
|
|
45
|
+
rescue StandardError
|
|
46
|
+
1000
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def advance_width(char)
|
|
50
|
+
gid = @unicode_to_gid[char.ord]
|
|
51
|
+
return 0 unless gid && @hmtx
|
|
52
|
+
|
|
53
|
+
metric = @hmtx.metric_for(gid)
|
|
54
|
+
metric ? metric[:advance_width] : 0
|
|
55
|
+
rescue StandardError
|
|
56
|
+
0
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def width_of_string(str, font_size:)
|
|
60
|
+
scale = font_size.to_f / units_per_em
|
|
61
|
+
str.each_char.sum { |c| advance_width(c) * scale }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def ascender(font_size: 12)
|
|
65
|
+
return font_size * 0.8 unless @hhea
|
|
66
|
+
|
|
67
|
+
((@hhea.ascender || 800).to_f / units_per_em) * font_size.to_f
|
|
68
|
+
rescue StandardError
|
|
69
|
+
font_size * 0.8
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def descender(font_size: 12)
|
|
73
|
+
return -font_size * 0.2 unless @hhea
|
|
74
|
+
|
|
75
|
+
((@hhea.descender || -200).to_f / units_per_em) * font_size.to_f
|
|
76
|
+
rescue StandardError
|
|
77
|
+
-font_size * 0.2
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def cap_height(font_size: 12)
|
|
81
|
+
return font_size * 0.7 unless @os2
|
|
82
|
+
|
|
83
|
+
((@os2.cap_height || 700).to_f / units_per_em) * font_size.to_f
|
|
84
|
+
rescue StandardError
|
|
85
|
+
font_size * 0.7
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def line_height(font_size:, line_spacing: 1.2)
|
|
89
|
+
# CSS/XSL convention: line-height is a multiplier of font-size,
|
|
90
|
+
# NOT derived from the font ascender/descender ratio.
|
|
91
|
+
font_size.to_f * line_spacing.to_f
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Arrolio::FontMetrics — glyph-width lookup.
|
|
4
|
+
module Arrolio
|
|
5
|
+
module FontMetrics
|
|
6
|
+
autoload :TrueTypeMetrics, 'arrolio/font_metrics/true_type_metrics'
|
|
7
|
+
autoload :AfmMetrics, 'arrolio/font_metrics/afm_metrics'
|
|
8
|
+
autoload :Registry, 'arrolio/font_metrics/registry'
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Arrolio
|
|
4
|
+
class Frame
|
|
5
|
+
attr_reader :x, :y, :width, :height, :consumed
|
|
6
|
+
|
|
7
|
+
def initialize(x:, y:, width:, height:, consumed: 0.0)
|
|
8
|
+
@x = x.to_f
|
|
9
|
+
@y = y.to_f
|
|
10
|
+
@width = width.to_f
|
|
11
|
+
@height = height.to_f
|
|
12
|
+
@consumed = consumed.to_f
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def remaining_height
|
|
16
|
+
@height - @consumed
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def full?
|
|
20
|
+
remaining_height <= 0.5
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def consume!(h)
|
|
24
|
+
@consumed += h.to_f if h.to_f.positive?
|
|
25
|
+
self
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def cursor_y
|
|
29
|
+
@y + @height - @consumed
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def clone_empty
|
|
33
|
+
self.class.new(x: @x, y: @y, width: @width, height: @height)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|