pd4ml 1.0.1 → 1.1.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e4513a87a893692d3e313518c97d69173def7204292ecaa262eb673661b0c701
4
- data.tar.gz: 9e6a2f7d64d1527a85b5cc223e55a0ffeee2d897e4775d34a72e4c54da47eaa7
3
+ metadata.gz: 5f3f328b0ad4e184f8d1b35bdf80f51c1e2708798e4e782a765e3c71d5d98ebc
4
+ data.tar.gz: 133a2003170431c83600e3d2eb7bdae3f12cb4af14c9edf365e38e95b9a9b25b
5
5
  SHA512:
6
- metadata.gz: c0be94f471af6f9c4d4ac9ddb963220cfa20ec1a0098eaab566643ec6f2ec3639420cf5fe736ec9a06a5d9e873e585a63bc91c2a0afe2fb9dec1c3964adeb7fd
7
- data.tar.gz: 11c4659971b4e152790e4418501d0c86815bbc977eb03860acff07107e96d99d3ac75f38ff23cb82e9c707b96e4557cf0e425d48b6c034db1223e076d8fe5a23
6
+ metadata.gz: fdd66e32394009f7a81e1a6cc5e51798bd0d798bde2463c0c5a2a81bf30b5c945afd1723a5a7fa2909e842a801606fdcfa7e5bdbedd9f43c46388d694525e6ff
7
+ data.tar.gz: f95260d4d7e77b25563a7f23f3ad00d58c6ed0a7e9f77b11d13f9a36e128e6878f951811b67e35d69bea5c4d2ee9f38a5772bab0e4d685540a7886e3535ad9db
data/README.md CHANGED
@@ -29,11 +29,17 @@ Requirements:
29
29
  - Ruby 3.1 or later.
30
30
  - Java 8 or later, found through `PD4ML_JAVA`, `JAVA_HOME` or the `PATH`.
31
31
 
32
- PD4ML itself is downloaded from <https://pd4ml.com/maven2>: the most recent 4.x release, plus the
33
- Bouncy Castle jars it needs for encryption and signing. Every file is checked against its SHA-1
34
- checksum. The download happens with `pd4ml install`, or on first use (`PD4ML_AUTO_INSTALL=0`
35
- turns that off). Builds are stored per user (`%LOCALAPPDATA%\pd4ml-ruby`, `~/.cache/pd4ml-ruby`,
36
- or `PD4ML_HOME`).
32
+ PD4ML itself is downloaded from <https://pd4ml.com/maven2>: by default the most recent release from
33
+ 4.2.0 on, plus the Bouncy Castle jars it needs for encryption and signing. Every file is checked
34
+ against its SHA-1 checksum. The download happens with `pd4ml install`, or on first use
35
+ (`PD4ML_AUTO_INSTALL=0` turns that off). Builds are stored per user
36
+ (`%LOCALAPPDATA%\pd4ml-ruby`, `~/.cache/pd4ml-ruby`, or `PD4ML_HOME`).
37
+
38
+ The gem's PD4ML 4.2 API (`read_eml`/`read_mht`, `add_allowed_resource_location`, XMP
39
+ `add_metadata`, the DOM API) needs PD4ML 4.2. Until PD4ML 4.2.0 is released, the default is
40
+ therefore the most recent 4.2.x development snapshot (4.2.0-SNAPSHOT). Once 4.2.0 is out, new
41
+ installs take it, and `pd4ml install` updates existing ones. `pd4ml install 4.1.2` installs the
42
+ current 4.1 release instead (the rest of the API works with it). See [Install PD4ML](docs/guide.md#22-install-pd4ml).
37
43
 
38
44
  To use a PD4ML jar of your own, set `PD4ML_CLASSPATH`, set `PD4ML_CLASSPATH_FILE`, or call
39
45
  `Pd4ml.configure(classpath: [...])`.
@@ -51,10 +57,16 @@ The gem adds Ruby I/O on top: paths, URLs, binary Strings and IO objects in and
51
57
  for progress listeners. Several processes can share one bridge server: run `pd4ml serve` and set
52
58
  `PD4ML_BRIDGE=tcp://127.0.0.1:8723`.
53
59
 
60
+ With PD4ML 4.2, the gem also reads e-mail messages and saved web pages (`read_eml`, `read_mht`; DMS
61
+ and UA editions),
62
+ and gives access to the parsed document before it is written (`get_document`, `layout`: the DOM
63
+ API of the DMS and UA editions, whose lists come back as Ruby Arrays). See the
64
+ [guide](docs/guide.md#516-e-mail-messages-and-saved-web-pages).
65
+
54
66
  ## Command line
55
67
 
56
68
  ```
57
- pd4ml install [VERSION] [--snapshots] download PD4ML (or update it)
69
+ pd4ml install [VERSION] [--snapshots] download PD4ML (default: 4.2, see above) or update it
58
70
  pd4ml list versions in the repository
59
71
  pd4ml version the PD4ML version in use
60
72
  pd4ml convert in.html out.pdf [--page-size A4 --landscape --spec pdf/a-2b ...]
data/docs/api.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  _Generated by `tools/gen_api.rb` from the PD4ML Java classes - do not edit._
4
4
 
5
- Every public PD4ML class is a Ruby class in the `Pd4ml` module. Methods can be called by their Java name (`setPageSize`) or Ruby-style (`set_page_size`; `isDemoMode` as `demo_mode?`); both are listed below. Constants and static fields are Ruby constants or class methods (`Pd4ml::PageSize::A4`, `Pd4ml::LayoutEngine.v42`), public instance fields are read with `obj[:name]`. Java types appear as Ruby types: binary Strings for `byte[]` and `InputStream`, a path or URL String for `URL`/`File`, `JavaObject` for any other Java object. Descriptions of each method are in the [PD4ML manual](https://pd4ml.com/docs/pd4ml-manual.html).
5
+ Every public PD4ML class is a Ruby class in the `Pd4ml` module. Methods can be called by their Java name (`setPageSize`) or Ruby-style (`set_page_size`; `isDemoMode` as `demo_mode?`); both are listed below. Constants and static fields are Ruby constants or class methods (`Pd4ml::PageSize::A4`, `Pd4ml::LayoutEngine.v42`), public instance fields are read with `obj[:name]`. Java types appear as Ruby types: binary Strings for `byte[]` and `InputStream`, a path or URL String for `URL`/`File`, `JavaObject` for any other Java object. The DOM API classes (`Pd4ml::Dom`: `PD4ML#get_document` and `PD4ML#layout` return them; v42 layout engine, DMS and UA editions) return Java lists and sets as Ruby Arrays, maps as Hashes. Descriptions of each method are in the [PD4ML manual](https://pd4ml.com/docs/pd4ml-manual.html).
6
6
 
7
7
  ## Pd4ml::PD4ML
8
8
 
@@ -78,6 +78,8 @@ keep_trailing_blank_pages?() -> true/false # isKeepTrailingBlankPages
78
78
  test_termination() -> nil # testTermination
79
79
  get_height(width: Integer) -> Integer # getHeight
80
80
  get_document_title() -> String # getDocumentTitle
81
+ layout() -> Dom::Layout # layout
82
+ get_document() -> Dom::Document # getDocument
81
83
  set_rendering_hints(hints: Hash) -> nil # setRenderingHints
82
84
  dump(mode: Integer) -> String # dump
83
85
  protect_physical_unit_dimensions(keep_physical_unit_dimensions: true/false) -> nil # protectPhysicalUnitDimensions
@@ -85,10 +87,12 @@ Pd4ml::PD4ML.translate_to_pt(mm: Float) -> Float # translateToPt
85
87
  Pd4ml::PD4ML.predict_scale(page_margins: PageMargins, page_size: PageSize, user_space: Integer) -> Float # predictScale
86
88
  Pd4ml::PD4ML.predict_page_height(page_margins: PageMargins, page_size: PageSize, user_space: Integer) -> Integer # predictPageHeight
87
89
  set_dynamic_data(map: Hash) -> nil # setDynamicData
90
+ add_allowed_resource_location(locations: String) -> nil # addAllowedResourceLocation
88
91
  set_param(key: String, value: String) -> nil # setParam
89
92
  get_last_render_info(key: String) -> Object # getLastRenderInfo
90
93
  output_range(description: String) -> nil # outputRange
91
94
  add_metadata(key: String, value: String, hide: true/false) -> nil # addMetadata
95
+ add_metadata(namespace_uri: String, prefix: String, key: String, value: String) -> nil # addMetadata
92
96
  apply_kerning(enable: true/false) -> nil # applyKerning
93
97
  enable_ligatures(enable: true/false) -> nil # enableLigatures
94
98
  get_cache() -> Object # getCache
@@ -119,7 +123,6 @@ async_resource_loader(thread_pool_size: Integer) -> nil # asyncResourceLoader
119
123
  | `Pd4ml::Constants::PD4ML_EXTRA_RESOURCE_LOADERS` | String | static read-only |
120
124
  | `Pd4ml::Constants::PD4ML_DOCUMENT_VIEW_MODE` | String | static read-only |
121
125
  | `Pd4ml::Constants::PD4ML_DOCUMENT_DATE` | String | static read-only |
122
- | `Pd4ml::Constants::PD4ML_WORD_EDITABLE` | String | static read-only |
123
126
  | `Pd4ml::Constants::PD4ML_DOCUMENT_XMP_DATE` | String | static read-only |
124
127
  | `Pd4ml::Constants::PD4ML_FAKE_BROWSER` | String | static read-only |
125
128
  | `Pd4ml::Constants::PD4ML_CACHE_ENABLE` | String | static read-only |
@@ -398,6 +401,7 @@ requires_metadata() -> true/false # requiresMetadata
398
401
  requires_output_internet() -> true/false # requiresOutputInternet
399
402
  allows_external_hyperlinking() -> true/false # allowsExternalHyperlinking
400
403
  requires_reliable_unicode_semantics() -> true/false # requiresReliableUnicodeSemantics
404
+ has_pdf_version() -> true/false # hasPdfVersion
401
405
  get_pdf_version_label() -> String # getPdfVersionLabel
402
406
  get_pdf_ext_level() -> Integer # getPdfExtLevel
403
407
  get_pdfa_version() -> Integer # getPdfaVersion
@@ -450,7 +454,7 @@ Pd4ml::PdfDocumentBase.compute_scope(scope: String, page_num: Integer) -> Array<
450
454
 
451
455
  ## Pd4ml::PdfDocument
452
456
 
453
- `com.pd4ml.PdfDocument`
457
+ `com.pd4ml.PdfDocument` — also the methods of `Pd4ml::PdfDocumentBase`
454
458
 
455
459
  ```ruby
456
460
  Pd4ml::PdfDocument.new(bytes: String (binary), password: String)
@@ -544,3 +548,151 @@ error?() -> true/false # isError
544
548
  warning?() -> true/false # isWarning
545
549
  get_message() -> String # getMessage
546
550
  ```
551
+
552
+ ## Pd4ml::Dom::Node
553
+
554
+ `com.pd4ml.dom.Node` (DOM API)
555
+
556
+ ```ruby
557
+ get_node_name() -> String # getNodeName
558
+ get_parent_node() -> Dom::Node # getParentNode
559
+ get_parent_element() -> Dom::Element # getParentElement
560
+ get_owner_document() -> Dom::Document # getOwnerDocument
561
+ get_child_nodes() -> Array<Dom::Node> # getChildNodes
562
+ has_child_nodes() -> true/false # hasChildNodes
563
+ get_first_child() -> Dom::Node # getFirstChild
564
+ get_last_child() -> Dom::Node # getLastChild
565
+ get_next_sibling() -> Dom::Node # getNextSibling
566
+ get_previous_sibling() -> Dom::Node # getPreviousSibling
567
+ get_text_content() -> String # getTextContent
568
+ set_text_content(text: String) -> nil # setTextContent
569
+ append_child(child: Dom::Node) -> Dom::Node # appendChild
570
+ insert_before(child: Dom::Node, ref: Dom::Node) -> Dom::Node # insertBefore
571
+ replace_child(replacement: Dom::Node, old: Dom::Node) -> Dom::Node # replaceChild
572
+ remove_child(child: Dom::Node) -> Dom::Node # removeChild
573
+ remove() -> nil # remove
574
+ clone_node(deep: true/false) -> Dom::Node # cloneNode
575
+ contains(other: Dom::Node) -> true/false # contains
576
+ to_html() -> String # toHtml
577
+ ```
578
+
579
+ ## Pd4ml::Dom::Document
580
+
581
+ `com.pd4ml.dom.Document` (DOM API) — also the methods of `Pd4ml::Dom::Node`
582
+
583
+ ```ruby
584
+ get_document_element() -> Dom::Element # getDocumentElement
585
+ get_head() -> Dom::Element # getHead
586
+ get_body() -> Dom::Element # getBody
587
+ get_doctype() -> Dom::DocumentType # getDoctype
588
+ quirks_mode?() -> true/false # isQuirksMode
589
+ get_base_uri() -> String # getBaseURI
590
+ get_title() -> String # getTitle
591
+ get_element_by_id(id: String) -> Dom::Element # getElementById
592
+ get_elements_by_tag_name(name: String) -> Array<Dom::Element> # getElementsByTagName
593
+ get_elements_by_class_name(names: String) -> Array<Dom::Element> # getElementsByClassName
594
+ query_selector(selectors: String) -> Dom::Element # querySelector
595
+ query_selector_all(selectors: String) -> Array<Dom::Element> # querySelectorAll
596
+ create_element(tag_name: String) -> Dom::Element # createElement
597
+ create_text_node(data: String) -> Dom::Text # createTextNode
598
+ create_comment(data: String) -> Dom::Comment # createComment
599
+ set_text_content(text: String) -> nil # setTextContent
600
+ ```
601
+
602
+ ## Pd4ml::Dom::Element
603
+
604
+ `com.pd4ml.dom.Element` (DOM API) — also the methods of `Pd4ml::Dom::Node`
605
+
606
+ ```ruby
607
+ get_tag_name() -> String # getTagName
608
+ get_attribute(name: String) -> String # getAttribute
609
+ has_attribute(name: String) -> true/false # hasAttribute
610
+ set_attribute(name: String, value: String) -> nil # setAttribute
611
+ remove_attribute(name: String) -> nil # removeAttribute
612
+ get_attributes() -> Hash # getAttributes
613
+ get_id() -> String # getId
614
+ set_id(id: String) -> nil # setId
615
+ get_class_name() -> String # getClassName
616
+ set_class_name(class_name: String) -> nil # setClassName
617
+ get_class_list() -> Array<String> # getClassList
618
+ has_class(name: String) -> true/false # hasClass
619
+ add_class(name: String) -> nil # addClass
620
+ remove_class(name: String) -> nil # removeClass
621
+ toggle_class(name: String) -> true/false # toggleClass
622
+ get_children() -> Array<Dom::Element> # getChildren
623
+ get_first_element_child() -> Dom::Element # getFirstElementChild
624
+ get_last_element_child() -> Dom::Element # getLastElementChild
625
+ get_next_element_sibling() -> Dom::Element # getNextElementSibling
626
+ get_previous_element_sibling() -> Dom::Element # getPreviousElementSibling
627
+ get_elements_by_tag_name(name: String) -> Array<Dom::Element> # getElementsByTagName
628
+ get_elements_by_class_name(names: String) -> Array<Dom::Element> # getElementsByClassName
629
+ query_selector(selectors: String) -> Dom::Element # querySelector
630
+ query_selector_all(selectors: String) -> Array<Dom::Element> # querySelectorAll
631
+ matches(selectors: String) -> true/false # matches
632
+ closest(selectors: String) -> Dom::Element # closest
633
+ get_inner_html() -> String # getInnerHTML
634
+ set_inner_html(html: String) -> nil # setInnerHTML
635
+ get_outer_html() -> String # getOuterHTML
636
+ set_outer_html(html: String) -> nil # setOuterHTML
637
+ set_text_content(text: String) -> nil # setTextContent
638
+ ```
639
+
640
+ ## Pd4ml::Dom::Text
641
+
642
+ `com.pd4ml.dom.Text` (DOM API) — also the methods of `Pd4ml::Dom::Node`
643
+
644
+ ```ruby
645
+ get_data() -> String # getData
646
+ set_data(data: String) -> nil # setData
647
+ whitespace?() -> true/false # isWhitespace
648
+ set_text_content(text: String) -> nil # setTextContent
649
+ ```
650
+
651
+ ## Pd4ml::Dom::Comment
652
+
653
+ `com.pd4ml.dom.Comment` (DOM API) — also the methods of `Pd4ml::Dom::Node`
654
+
655
+ ```ruby
656
+ get_data() -> String # getData
657
+ set_data(data: String) -> nil # setData
658
+ get_text_content() -> String # getTextContent
659
+ set_text_content(text: String) -> nil # setTextContent
660
+ ```
661
+
662
+ ## Pd4ml::Dom::DocumentType
663
+
664
+ `com.pd4ml.dom.DocumentType` (DOM API) — also the methods of `Pd4ml::Dom::Node`
665
+
666
+ ```ruby
667
+ get_name() -> String # getName
668
+ get_public_id() -> String # getPublicId
669
+ get_system_id() -> String # getSystemId
670
+ set_text_content(text: String) -> nil # setTextContent
671
+ ```
672
+
673
+ ## Pd4ml::Dom::Layout
674
+
675
+ `com.pd4ml.dom.Layout` (DOM API)
676
+
677
+ ```ruby
678
+ get_page_count() -> Integer # getPageCount
679
+ get_page_width(page: Integer) -> Float # getPageWidth
680
+ get_page_height(page: Integer) -> Float # getPageHeight
681
+ get_page_number(page: Integer) -> Integer # getPageNumber
682
+ get_boxes(element: Dom::Element) -> Array<Dom::LayoutBox> # getBoxes
683
+ get_pages(element: Dom::Element) -> Array<Integer> # getPages
684
+ get_computed_style(element: Dom::Element, property: String) -> String # getComputedStyle
685
+ element_at(page: Integer, x: Float, y: Float) -> Dom::Element # elementAt
686
+ ```
687
+
688
+ ## Pd4ml::Dom::LayoutBox
689
+
690
+ `com.pd4ml.dom.LayoutBox` (DOM API)
691
+
692
+ ```ruby
693
+ get_page() -> Integer # getPage
694
+ get_x() -> Float # getX
695
+ get_y() -> Float # getY
696
+ get_width() -> Float # getWidth
697
+ get_height() -> Float # getHeight
698
+ ```
data/docs/guide.md CHANGED
@@ -34,7 +34,7 @@ The generated reference of every class and method is [api.md](api.md).
34
34
  | **Java 8 or later** | A JDK or JRE; an LTS release (17, 21) is recommended. The gem finds it through `PD4ML_JAVA`, then `JAVA_HOME`, then `java` on the `PATH`. |
35
35
  | **PD4ML** | Downloaded automatically on first use, or ahead of time with `pd4ml install` (see below); or any PD4ML build you point the gem at. |
36
36
  | **Network access** | Only for downloading PD4ML. Conversions need none, unless the HTML references remote resources. |
37
- | **A PD4ML license** | Optional for evaluation. Without one, PD4ML runs in evaluation mode and watermarks its output. See [Licensing](https://pd4ml.com/docs/#3.-licensing) and [5.18](#518-licensing). |
37
+ | **A PD4ML license** | Optional for evaluation. Without one, PD4ML runs in evaluation mode and watermarks its output. See [Licensing](https://pd4ml.com/docs/#3.-licensing) and [5.21](#521-licensing). |
38
38
 
39
39
  Check Java with `java -version`. On a server without Java, install a JRE package (for example
40
40
  `apt install openjdk-21-jre-headless`) and, if it is not on the `PATH`, set `JAVA_HOME`. In
@@ -62,9 +62,9 @@ The gem contains the Ruby library and the bridge only. The PD4ML library itself
62
62
  PD4ML Maven repository (<https://pd4ml.com/maven2>), in one of two ways.
63
63
 
64
64
  **Automatically, on first use.** If no PD4ML build is installed or configured
65
- ([2.3](#23-other-ways-to-provide-pd4ml)), the first use of PD4ML in a process downloads the most
66
- recent PD4ML 4.x release before starting Java. A warning on standard error reports it. So once the gem
67
- is installed, this is all it takes:
65
+ ([2.3](#23-other-ways-to-provide-pd4ml)), the first use of PD4ML in a process downloads PD4ML 4.2
66
+ ([which build](#which-build)) before starting Java. A warning on standard error reports it. So
67
+ once the gem is installed, this is all it takes:
68
68
 
69
69
  <!-- no-test -->
70
70
  ```ruby
@@ -82,7 +82,7 @@ pd4ml install # with Bundler: bundle exec pd4ml install
82
82
 
83
83
  Either way, the gem:
84
84
 
85
- - downloads the most recent PD4ML 4.x release;
85
+ - downloads PD4ML 4.2 ([which build](#which-build));
86
86
  - downloads the Bouncy Castle jars PD4ML needs for encryption and signing, from Maven Central;
87
87
  - checks every file against its published SHA-1 checksum;
88
88
  - stores them in a per-user cache directory: `%LOCALAPPDATA%\pd4ml-ruby` on Windows,
@@ -91,12 +91,29 @@ Either way, the gem:
91
91
  The gem then uses that build. Later runs do not check for newer PD4ML releases: run
92
92
  `pd4ml install` again to update. Updating the gem does not change the PD4ML build.
93
93
 
94
+ <a id="which-build"></a>**Which build.** The gem needs PD4ML 4.2 for its 4.2 API: `read_eml` and
95
+ `read_mht`, `add_allowed_resource_location`, `add_metadata` (XMP), and the DOM API
96
+ (`get_document`, `layout`), as well as for the v42 layout engine ([4.3](#43-layout-engines)). So a
97
+ plain `pd4ml install` (and the first use) takes:
98
+
99
+ - the most recent PD4ML release from 4.2.0 on;
100
+ - while 4.2.0 is not released yet, the most recent 4.2.x **development snapshot** (today
101
+ 4.2.0-SNAPSHOT), because no release has the 4.2 API yet.
102
+
103
+ Once 4.2.0 is released, new installs take it by themselves, and `pd4ml install` updates an
104
+ existing snapshot install to it. Snapshots of later versions (4.3, 5.0) are never taken by
105
+ default; `--snapshots` asks for them.
106
+
107
+ The rest of the API works with older builds too: to stay on the current 4.1 release, install it
108
+ explicitly (`pd4ml install 4.1.2`); the 4.2 methods then raise `Pd4ml::JavaError`
109
+ (`NoSuchMethodException`).
110
+
94
111
  | Option | |
95
112
  |---|---|
96
- | *(none)* | the most recent 4.x release, or an update to it |
113
+ | *(none)* | the most recent release from 4.2.0 on, else the most recent 4.2.x snapshot ([which build](#which-build)), or an update to it |
97
114
  | `--snapshots` | the most recent 4.x build, including development snapshots |
98
- | `pd4ml install 4.0.23fx2` | an exact version |
99
- | `--jakarta` | the `-jakarta` variant (for Jakarta EE / Servlet 6 environments) |
115
+ | `pd4ml install 4.1.2` | an exact version (any release or snapshot the repository lists) |
116
+ | `--jakarta` | for Jakarta EE / Servlet 6 environments: a `-jakarta` variant if one fits; PD4ML 4.2 has none (its builds contain the Jakarta tag library), so the same 4.2 build (an older `-jakarta` build by version, e.g. `pd4ml install 4.0.20fx3-jakarta`) |
100
117
  | `--home DIR` | install into `DIR` (default: `PD4ML_HOME`, else the cache directory) |
101
118
  | `--force` | download again |
102
119
  | `--no-bouncycastle` | skip the Bouncy Castle jars (encryption and signing will not work) |
@@ -252,6 +269,10 @@ such a build:
252
269
  - **Objects**: a Java object comes back as a `Pd4ml::JavaObject` with all its Java methods.
253
270
  Public fields: `obj[:width]` and `obj[:width] = value`. `to_s` is Java's `toString()`,
254
271
  `enum_name` an enum constant's name, `java_class` the class name.
272
+ - **Collections**: a Java `List` or `Map` generally comes back as a `JavaObject` (`size`,
273
+ `get(i)`). The DOM API ([5.18](#518-modifying-the-document-before-conversion-dom-api)) is the
274
+ exception: its lists and sets come back as Ruby Arrays and its maps as Hashes. `call_java` returns
275
+ them as Java objects.
255
276
  - **Values**: Strings, Integers, Floats (`100.0` goes to Java as a float), `true`/`false`, `nil`,
256
277
  Symbols (as strings: `engine: :v40`), Arrays, Hashes (as Java `Map`s).
257
278
  - **Binary data**: a binary String (`File.binread`, `"...".b`, encoding ASCII-8BIT) goes to Java
@@ -529,9 +550,8 @@ The image formats are `PNG8`, `PNG24`, `TIFF` and `TIFF_COMPRESSED` (constants i
529
550
  The TIFF formats need Java 9 or later.
530
551
  The v42 engine does not support the multi-page TIFF formats yet; use the v40 engine for them.
531
552
 
532
- With the v42 engine, DOCX/RTF output can be made *editable*: positioned content becomes text
533
- boxes and form fields instead of pictures. To enable it, call
534
- `pd.set_param(Pd4ml::Constants::PD4ML_WORD_EDITABLE, 'true')`.
553
+ With the v42 engine, DOCX/RTF output is *editable*: positioned content becomes text boxes
554
+ and form fields, not pictures.
535
555
 
536
556
  ### 5.12 Interactive forms and multi-column layout
537
557
 
@@ -583,6 +603,7 @@ pd.set_log_level(1) # 0 (silent) .. 5
583
603
  pd.set_author_name('Reporting service')
584
604
  pd.set_document_title('Monthly statement')
585
605
  pd.add_metadata('StatementId', '2026-10', false)
606
+ pd.add_metadata('http://www.w3.org/ns/tdmrep/', 'tdm', 'reservation', '1') # XMP, a namespace of your own
586
607
  pd.read_html_string('<h1>Statement</h1>')
587
608
  pd.write_pdf('statement.pdf')
588
609
  pages = pd.get_last_render_info(Pd4ml::Constants::PD4ML_TOTAL_PAGES).to_i
@@ -593,6 +614,14 @@ itself. An error it raises comes out of the call that caused it, as a `Pd4ml::Ca
593
614
  The v42 engine currently reports fewer progress stages than v40. More in
594
615
  [extensibility and diagnostics](https://pd4ml.com/docs/#12.-extensibility-and-diagnostics).
595
616
 
617
+ `add_metadata(key, value, hide)` adds a custom property to the PDF's XMP metadata, which viewers
618
+ show among the custom document properties (`hide`: true keeps it out of their view).
619
+ `add_metadata(namespace_uri, prefix, key, value)` (PD4ML 4.2) adds an XMP property in a
620
+ namespace of your own; the example writes `<tdm:reservation>1</tdm:reservation>`, which tells
621
+ text and data mining crawlers the rights are reserved (the W3C TDM Reservation Protocol). In PDF/A
622
+ output, PD4ML declares the namespace in a PDF/A extension schema. A reserved prefix (`rdf`, `xmp`,
623
+ `dc`, `pdf`, `pd4ml`...) raises `Pd4ml::JavaError` (`IllegalArgumentException`).
624
+
596
625
  ### 5.15 Working with existing PDFs
597
626
 
598
627
  ```ruby
@@ -621,7 +650,136 @@ PD4ML's PDF toolkit (signing, optimisation, XFDF) is described in
621
650
  [the PDF post-processing toolkit](https://pd4ml.com/docs/#15.-the-pdf-post-processing-toolkit)
622
651
  and is reachable through `Pd4ml::Java.type(...)`.
623
652
 
624
- ### 5.16 Web applications
653
+ ### 5.16 E-mail messages and saved web pages
654
+
655
+ > **Editions.** E-mail and saved web page input — `read_eml`/`read_mht`, and `read_html` for input it
656
+ > recognizes as one of them — is part of the PD4ML **DMS and UA editions**. With an STD or PRO license it
657
+ > works, but the output carries the "PD4ML EVALUATION MODE" banners.
658
+
659
+ PD4ML 4.2 reads e-mail messages (`.eml`) and web pages saved as a single file (`.mht`, `.mhtml`)
660
+ and writes them to any output format:
661
+
662
+ ```ruby
663
+ require 'pd4ml'
664
+
665
+ pd = Pd4ml::PD4ML.new
666
+ pd.read_eml('message.eml') # a path or URL, or the message itself: File.binread, an IO
667
+ pd.write_pdf('message.pdf') # the attachments are embedded in the PDF
668
+
669
+ page = Pd4ml::PD4ML.new
670
+ page.read_mht('page.mht') # a saved web page, with its images, styles and fonts
671
+ page.write_pdf('page.pdf')
672
+ ```
673
+
674
+ - A message becomes a block with From, Date, To, Cc, Subject and the attachments, followed by its
675
+ body: the HTML version if there is one, otherwise the plain text, with the images it embeds. The
676
+ attached files are embedded in the PDF next to their names (PDF/A-1 and PDF/A-2 and the other
677
+ outputs keep only the names).
678
+ - A saved page shows as it was saved, with the resources saved in the file; its links lead where
679
+ they led on the original page.
680
+ - `read_html` recognizes these files too, by the `.eml`, `.mht` or `.mhtml` ending or by how the
681
+ content starts. For mail from outside your control, use `read_eml` and `read_mht`.
682
+ - Of the resources a file refers to but does not contain, only `https://` ones are loaded.
683
+ `add_allowed_resource_location` ([5.17](#517-untrusted-html-restricting-resource-loading))
684
+ replaces that list. A file read from a path or URL must then be within the allowed locations
685
+ itself; one passed as content (a binary String or IO) need not be.
686
+ - Scripts do not run, the content of frames is not shown, and encrypted (S/MIME, PGP) messages
687
+ are not decrypted.
688
+
689
+ See [converting e-mail and saved web pages](https://pd4ml.com/docs/features/converting-e-mail-and-saved-web-pages.html).
690
+
691
+ ### 5.17 Untrusted HTML: restricting resource loading
692
+
693
+ HTML from users, customers or other systems decides what PD4ML loads: `<img src="file:/etc/...">`
694
+ reads local files, `http://169.254.169.254/...` calls services inside your network.
695
+ `add_allowed_resource_location` (PD4ML 4.2) limits loading to the locations you list:
696
+
697
+ ```ruby
698
+ require 'pd4ml'
699
+
700
+ user_html = '<p>Comment <img src="file:/etc/hosts"> <img src="http://169.254.169.254/latest/meta-data/"></p>'
701
+ pd = Pd4ml::PD4ML.new
702
+ pd.add_allowed_resource_location('https://cdn.example.com/assets/')
703
+ pd.add_allowed_resource_location('https://*.example.org/, file:/opt/app/templates/')
704
+ pd.read_html_string(user_html, 'https://cdn.example.com/assets/')
705
+ pd.write_pdf('comment.pdf') # without the two images
706
+ ```
707
+
708
+ - `locations` is a comma-separated list of URL prefixes; each call adds to the list. `*` matches
709
+ any characters except `/` (one host name or path segment). The comparison ignores case, and a
710
+ path containing `..` is always rejected.
711
+ - It covers style sheets, images, web fonts and attachments, and the document itself when it is
712
+ read from a path or URL. `data:` URIs, `java:` resources and the font directories of `use_ttf`
713
+ are not affected.
714
+ - A rejected resource is left out and the conversion goes on;
715
+ `set_error_policy(Pd4ml::ErrorPolicy::PEDANTIC)` makes it fail with a `Pd4ml::JavaError`
716
+ instead, and `set_log_level(Pd4ml::Constants::LOG_CACHE)` logs what was rejected.
717
+ - With no location added, resources load from anywhere. Call it before `read_html`.
718
+
719
+ See [restricting resource loading for untrusted HTML](https://pd4ml.com/docs/features/restricting-resource-loading-for-untrusted-html.html).
720
+
721
+ ### 5.18 Modifying the document before conversion (DOM API)
722
+
723
+ With the v42 layout engine, the document `read_html` parsed can be read, queried with CSS
724
+ selectors and changed before it is written; `layout` lays it out into pages without writing it
725
+ and tells where each element ended up.
726
+
727
+ > The DOM API (`get_document`, `layout` and the `Pd4ml::Dom` classes) is part of the **DMS and UA
728
+ > editions** of PD4ML 4.2. Without a license it can be evaluated (the output carries the
729
+ > evaluation watermark); with an STD or PRO license it works, but the output of a document it was
730
+ > used on carries "PD4ML EVALUATION MODE" banners. See [editions](https://pd4ml.com/products/).
731
+
732
+ ```ruby
733
+ require 'pd4ml'
734
+
735
+ pd = Pd4ml::PD4ML.new # the v42 engine
736
+ pd.read_html_string(<<~HTML)
737
+ <h1 id="title">Invoice</h1>
738
+ <table class="items"><tr><td>Consulting</td><td class="price">1 200,00</td></tr></table>
739
+ <img src="logo.png">
740
+ <div id="totals">Total: <span id="total"></span></div>
741
+ HTML
742
+
743
+ doc = pd.get_document
744
+ doc.get_element_by_id('total').set_text_content('1 250,00 EUR')
745
+ doc.query_selector_all('img:not([alt])').each { |img| img.set_attribute('alt', '') } # a Ruby Array
746
+ doc.query_selector('table.items').add_class('striped')
747
+ note = doc.create_element('p')
748
+ note.set_inner_html('Payable within <b>30 days</b>.')
749
+ doc.get_body.append_child(note)
750
+
751
+ layout = pd.layout # the pages as write_pdf would make them
752
+ totals = doc.get_element_by_id('totals')
753
+ totals.set_attribute('style', 'page-break-before: always') if layout.get_pages(totals).size > 1
754
+ layout.get_boxes(totals).each do |box|
755
+ puts format('page %d: %.1f, %.1f, %.1f x %.1f pt', box.get_page, box.get_x, box.get_y, box.get_width, box.get_height)
756
+ end
757
+ puts "#{layout.get_page_count} page(s), total in #{layout.get_computed_style(totals, 'color')}"
758
+ pd.write_pdf('invoice.pdf')
759
+ ```
760
+
761
+ | | Methods (Ruby-style; the Java names work too) |
762
+ |---|---|
763
+ | `Dom::Document` (`pd.get_document`) | `get_element_by_id`, `query_selector`, `query_selector_all`, `get_elements_by_tag_name`, `get_elements_by_class_name`, `get_head`, `get_body`, `get_title`, `create_element`, `create_text_node`, `create_comment` |
764
+ | `Dom::Node` (every node) | `get_parent_node`, `get_child_nodes`, `get_first_child`, `get_next_sibling`..., `append_child`, `insert_before`, `replace_child`, `remove_child`, `remove`, `clone_node`, `contains`, `get_text_content`, `set_text_content`, `to_html` |
765
+ | `Dom::Element` | `get_attribute`, `set_attribute`, `remove_attribute`, `get_attributes` (a Hash), `get_id`, `get_class_list`, `add_class`, `remove_class`, `toggle_class`, `get_inner_html`, `set_inner_html`, `get_outer_html`, `set_outer_html`, `matches`, `closest`, `get_children`... and the queries of `Document` within the element |
766
+ | `Dom::Layout` (`pd.layout`) | `get_page_count`, `get_page_width(page)`, `get_page_height(page)`, `get_page_number(page)`, `get_boxes(element)`, `get_pages(element)`, `get_computed_style(element, property)`, `element_at(page, x, y)` |
767
+ | `Dom::LayoutBox` | `get_page`, `get_x`, `get_y`, `get_width`, `get_height` (PDF points from the top left corner of the page) |
768
+
769
+ - Lists (`query_selector_all`, `get_child_nodes`, `get_boxes`, `get_pages`...) come back as Ruby
770
+ Arrays, `get_attributes` as a Hash. They are snapshots: removing or moving nodes while going
771
+ through one is safe.
772
+ - Two objects for the same node are `==` (Java `equals`), but not necessarily the same object.
773
+ - A layout is a snapshot too: after changing the document, call `layout` again. Pages are
774
+ numbered from 1.
775
+ - Change the document before the first output; every later write lays out the changed document.
776
+ - `get_document` and `layout` raise `Pd4ml::JavaError` (`IllegalStateException`) before
777
+ `read_html` or with the v40 engine.
778
+
779
+ The full method list is in [api.md](api.md#pd4mldomnode); the PD4ML guide is
780
+ [modifying the HTML document before conversion](https://pd4ml.com/docs/features/modifying-the-html-document-before-conversion.html).
781
+
782
+ ### 5.19 Web applications
625
783
 
626
784
  Rails controller, with the HTML from a view:
627
785
 
@@ -658,7 +816,7 @@ To pay the cost of starting Java at boot instead of on the first request, touch
658
816
  initializer (`Pd4ml.version`). Each Puma worker process starts its own bridge; set
659
817
  `PD4ML_BRIDGE` to share one bridge server between them.
660
818
 
661
- ### 5.17 Batch conversion
819
+ ### 5.20 Batch conversion
662
820
 
663
821
  ```ruby
664
822
  require 'pd4ml'
@@ -674,7 +832,7 @@ end
674
832
  One bridge serves the whole loop. To convert in parallel, run several processes (each with its
675
833
  own bridge), or point them all at one bridge server.
676
834
 
677
- ### 5.18 Licensing
835
+ ### 5.21 Licensing
678
836
 
679
837
  <!-- no-test -->
680
838
  ```ruby
@@ -709,8 +867,9 @@ Without a license, PD4ML is fully functional but marks its output. See
709
867
  differently than in the browser, set `set_html_width` to the width the page was designed for.
710
868
  - **Index fonts once** with `-configure.fonts`, instead of pointing `use_ttf` at a large
711
869
  unindexed directory on every conversion.
712
- - **Restrict what documents can load** when converting untrusted HTML. Use `set_param` with
713
- `Constants::PD4ML_ALLOWED_RESOURCE_LOCATION` and `PD4ML_DISABLE_EXTERNAL_ATTACHMENTS`.
870
+ - **Restrict what documents can load** when converting untrusted HTML, with
871
+ `add_allowed_resource_location` ([5.17](#517-untrusted-html-restricting-resource-loading)); also
872
+ consider `set_param(Pd4ml::Constants::PD4ML_DISABLE_EXTERNAL_ATTACHMENTS, 'true')`.
714
873
  - **Pin the build in production.** Use `pd4ml install <version>` or a `PD4ML_CLASSPATH` to a
715
874
  jar you keep, rather than following the latest release.
716
875
 
@@ -739,6 +898,7 @@ Without a license, PD4ML is fully functional but marks its output. See
739
898
  |---|---|
740
899
  | `Pd4ml::PD4ML` | the converter: `Pd4ml::PD4ML.new(license:, html_width:, engine:)`; every Java method of `com.pd4ml.PD4ML` |
741
900
  | `PageSize`, `PageMargins`, `Units`, `Dimensions`, `Constants`, `LayoutEngine`, `ErrorPolicy`, `PdfSpec`, `PdfDocument`, `ProgressListener`, `StatusMessage`, `ResourceProvider`, `CustomTag` | the PD4ML Java classes ([api.md](api.md)) |
901
+ | `Dom::Document`, `Dom::Node`, `Dom::Element`, `Dom::Text`, `Dom::Comment`, `Dom::DocumentType`, `Dom::Layout`, `Dom::LayoutBox` | the DOM API classes, from `pd.get_document` and `pd.layout` ([5.18](#518-modifying-the-document-before-conversion-dom-api)); their lists come back as Arrays |
742
902
  | `html_to_pdf(source, target, base_url:, page_size:, page_margins:, spec:, engine:, html_width:)`, `open_pdf(source, password)`, `write_pdf(doc, target, password:, permissions:)`, `merge_pdfs(a, b, target)` | one-call conversion and PDF documents |
743
903
  | `version`, `layout_engines?`, `to_url(location)`, `pdf_spec(keyword)` | runtime helpers |
744
904
  | `configure(**options)`, `shutdown` | the bridge: `classpath:`, `java:`, `jvm_options:`, `server:`, `log_file:` |
@@ -752,6 +912,7 @@ Without a license, PD4ML is fully functional but marks its output. See
752
912
  |---|---|
753
913
  | `read_html(source, base_url = nil, encoding = nil)` | `source`: a path or URL (String, Pathname, URI), a binary String or IO, or a Java `URL`/`InputStream` |
754
914
  | `read_html_string(html, base_url = nil)` | the document as a String |
915
+ | `read_eml(source)`, `read_mht(source)` | an e-mail message, a saved web page ([5.16](#516-e-mail-messages-and-saved-web-pages)), DMS and UA editions; `source` as for `read_html` |
755
916
  | `write_pdf(target = nil, spec: nil)` | `target`: a path or IO; none: returns a binary String. `spec`: a `PdfSpec`, a keyword (`'pdf/a-2b'`) or `'PDF'`/`'PDFA'`/`'PDFUA'` |
756
917
  | `write_docx(target = nil)`, `write_rtf(target = nil, rtf_wmf: false)` | as `write_pdf` |
757
918
  | `render_as_images(format)`, `render_png` | pages as encoded images (binary Strings); `render_as_images(dir, name, format)` writes files |
data/exe/pd4ml CHANGED
@@ -5,11 +5,16 @@
5
5
  require 'optparse'
6
6
  require_relative '../lib/pd4ml'
7
7
 
8
+ MINIMUM = Pd4ml::Installer::MINIMUM_VERSION
9
+ LINE = MINIMUM.split('.')[0, 2].join('.')
8
10
  USAGE = <<~TEXT
9
11
  usage: pd4ml <command> [options]
10
12
 
11
13
  install [VERSION] [--snapshots] [--jakarta] [--home DIR] [--force] [--no-bouncycastle]
12
- download a PD4ML build (default: the most recent 4.* release), or update to it
14
+ download a PD4ML build, or update to it. Default: the most recent
15
+ release from #{MINIMUM} on; until #{MINIMUM} is out, the most recent #{LINE}.x
16
+ snapshot (the gem's #{LINE} API needs PD4ML #{LINE}). --snapshots: the most
17
+ recent 4.* build. VERSION: an exact one, e.g. 4.1.2
13
18
  list [--snapshots] [--jakarta]
14
19
  the versions in the PD4ML repository (* the installed one)
15
20
  version the PD4ML version the gem runs (downloads it on first use)
data/lib/pd4ml/bridge.rb CHANGED
@@ -66,7 +66,7 @@ module Pd4ml
66
66
  unless entries
67
67
  entries = Installer.installed_classpath
68
68
  if entries.empty? && Installer.auto_install?
69
- warn 'pd4ml: no PD4ML build installed yet: downloading the most recent release ' \
69
+ warn "pd4ml: no PD4ML build installed yet: downloading PD4ML #{Installer::MINIMUM_VERSION} or later " \
70
70
  "(once; 'pd4ml install' updates it, PD4ML_AUTO_INSTALL=0 turns this off)"
71
71
  Installer.install
72
72
  entries = Installer.installed_classpath
@@ -9,9 +9,10 @@ require 'time'
9
9
  module Pd4ml
10
10
  # Downloads a PD4ML build from the PD4ML Maven repository and makes it the gem's default:
11
11
  #
12
- # pd4ml install the most recent 4.* release
12
+ # pd4ml install the most recent release from MINIMUM_VERSION (4.2.0) on;
13
+ # until there is one, the most recent 4.2.x snapshot
13
14
  # pd4ml install --snapshots the most recent 4.* build, snapshots included
14
- # pd4ml install 4.0.23fx2 an exact version
15
+ # pd4ml install 4.1.2 an exact version
15
16
  #
16
17
  # or Pd4ml.install from code. The jar is stored in the user's cache directory (PD4ML_HOME to
17
18
  # choose another) with the Bouncy Castle jars PD4ML needs for encryption and signing, every
@@ -26,6 +27,9 @@ module Pd4ml
26
27
  BOUNCYCASTLE = %w[bcprov-jdk18on bcpkix-jdk18on bcutil-jdk18on].freeze
27
28
  MANIFEST = 'installed.json'
28
29
  VERSION_PATTERN = /\A(\d+)\.(\d+)\.(\d+)(?:fx(\d+)(?:a(\d+))?)?(-jakarta)?(-SNAPSHOT)?\z/
30
+ # The oldest PD4ML a plain install takes: the gem's PD4ML 4.2 API (read_eml/read_mht,
31
+ # add_allowed_resource_location, add_metadata, the DOM API) needs it.
32
+ MINIMUM_VERSION = '4.2.0'
29
33
 
30
34
  module_function
31
35
 
@@ -54,20 +58,55 @@ module Pd4ml
54
58
  sort_versions(candidates).last
55
59
  end
56
60
 
61
+ # The version a plain install takes: the most recent release of the minimum's major line (4.*)
62
+ # at least `minimum`; while there is none, the most recent snapshot of the minimum's line
63
+ # (4.2.x for 4.2.0); while there is none of those either (the -jakarta variant has no 4.2
64
+ # build yet), the most recent 4.* release, as before. So installs move to the release once it
65
+ # is out, and snapshots of later lines (or a 5.x) are never picked by accident. With
66
+ # `jakarta`, a -jakarta variant by that rule if there is one, else the plain build of the same
67
+ # rule: PD4ML 4.2 publishes no -jakarta variant, its builds contain the Jakarta tag library
68
+ # (com.pd4ml.taglib.jakarta); only without either, the most recent -jakarta release.
69
+ def default_version(versions, jakarta: false, minimum: MINIMUM_VERSION)
70
+ min = version_key(minimum)
71
+ (jakarta ? [true, false] : [false]).each do |variant|
72
+ candidates = versions.select do |v|
73
+ k = version_key(v)
74
+ k && k[0] == min[0] && v.include?('-jakarta') == variant
75
+ end
76
+ releases = candidates.select { |v| !v.end_with?('-SNAPSHOT') && (version_key(v) <=> min) >= 0 }
77
+ return sort_versions(releases).last unless releases.empty?
78
+
79
+ snapshots = candidates.select { |v| v.end_with?('-SNAPSHOT') && version_key(v)[0, 2] == min[0, 2] }
80
+ return sort_versions(snapshots).last unless snapshots.empty?
81
+ end
82
+ select_version(versions, major: min[0], jakarta: jakarta)
83
+ end
84
+
57
85
  # The versions the repository lists.
58
86
  def available_versions(repository = REPOSITORY)
59
- fetch("#{repository}/#{GROUP_PATH}/maven-metadata.xml").scan(%r{<version>([^<]+)</version>}).flatten.map(&:strip)
87
+ parse_versions(fetch("#{repository}/#{GROUP_PATH}/maven-metadata.xml"))
60
88
  end
61
89
 
90
+ # The versions a maven-metadata.xml document lists.
91
+ def parse_versions(xml) = xml.scan(%r{<version>([^<]+)</version>}).flatten.map(&:strip)
92
+
62
93
  # ------------------------------------------------------------------------------ install
63
94
 
64
95
  # Downloads a PD4ML build (unless already there) and makes it the default one; returns the
65
- # jar's path. version: 'latest' (the most recent 4.* release; with snapshots: true, the most
66
- # recent 4.* build) or an exact one such as '4.0.23fx2'.
96
+ # jar's path. version: 'latest' (default_version: the most recent release from MINIMUM_VERSION
97
+ # on, else the most recent 4.2.x snapshot; with snapshots: true, the most recent 4.* build) or
98
+ # an exact one such as '4.1.2' or '4.0.23fx2'.
67
99
  def install(version = 'latest', snapshots: false, jakarta: false, repository: REPOSITORY, home: nil,
68
100
  force: false, quiet: false, bouncycastle: true)
69
101
  home ||= default_home
70
- version = select_version(available_versions(repository), snapshots: snapshots, jakarta: jakarta) if version == 'latest'
102
+ if version == 'latest'
103
+ versions = available_versions(repository)
104
+ version = if snapshots
105
+ select_version(versions, snapshots: true, jakarta: jakarta)
106
+ else
107
+ default_version(versions, jakarta: jakarta)
108
+ end
109
+ end
71
110
  name = jar_name(version, repository)
72
111
  jar = File.join(home, name)
73
112
  download("#{repository}/#{GROUP_PATH}/#{version}/#{name}", jar, force, quiet)
data/lib/pd4ml/java.rb CHANGED
@@ -42,7 +42,9 @@ module Pd4ml
42
42
 
43
43
  # A Java object living in the bridge. Call its Java methods by their Java names or in Ruby
44
44
  # style (`size.rotate`, `pd.set_page_size(...)`, `pd.demo_mode?`). Public fields: `obj[:width]`
45
- # / `obj[:width] = v`. Released in Java when garbage collected.
45
+ # / `obj[:width] = v`. Released in Java when garbage collected. The DOM API's objects
46
+ # (com.pd4ml.dom) return their Java lists and sets as Ruby Arrays, maps as Hashes
47
+ # (`doc.query_selector_all('p').each { ... }`); `call_java` returns them as they are.
46
48
  class JavaObject < BasicObject
47
49
  def initialize(bridge, handle, java_class, enum_name)
48
50
  @bridge = bridge
@@ -110,7 +112,9 @@ module Pd4ml
110
112
  ::Kernel.raise ::NoMethodError, "set Java fields with obj[:#{name.to_s.chomp('=')}] = value" if name.to_s.end_with?('=')
111
113
  ::Kernel.raise ::ArgumentError, 'Java methods take no block' if block
112
114
 
113
- @bridge.call(self, Naming.resolve(@bridge, @java_class, name, false), args)
115
+ result = @bridge.call(self, Naming.resolve(@bridge, @java_class, name, false), args)
116
+ # the DOM API's lists, sets and maps are snapshots: Ruby Arrays and Hashes
117
+ @java_class.start_with?('com.pd4ml.dom.') ? Java.to_ruby(result) : result
114
118
  end
115
119
 
116
120
  def respond_to_missing?(_name, _include_private = false) = true
@@ -205,5 +209,21 @@ module Pd4ml
205
209
 
206
210
  Bridge.get.proxy(iface, &block)
207
211
  end
212
+
213
+ # A Java collection (List, Set...) as a Ruby Array and a Java Map as a Hash, in one or two
214
+ # bridge calls; any other value as it is.
215
+ def self.to_ruby(value)
216
+ return value unless value.is_a?(JavaObject) && value.java_class.start_with?('java.util.')
217
+
218
+ if value.instance_of_java?('java.util.Collection')
219
+ value.call_java('toArray')
220
+ elsif value.instance_of_java?('java.util.Map')
221
+ keys = value.call_java('keySet').call_java('toArray')
222
+ values = value.call_java('values').call_java('toArray')
223
+ keys.zip(values).to_h
224
+ else
225
+ value
226
+ end
227
+ end
208
228
  end
209
229
  end
Binary file
data/lib/pd4ml/pd4ml.rb CHANGED
@@ -5,7 +5,9 @@ module Pd4ml
5
5
  # engine by default. Every Java method is available by its Java name or in Ruby style
6
6
  # (`set_page_size`, `add_style`, `use_ttf`); these take Ruby I/O: read_html (a path, URL or
7
7
  # binary String/IO), read_html_string, write_pdf/write_docx/write_rtf (the document returned as
8
- # a binary String, or written to a path or IO), render_as_images and monitor_progress_with (a block).
8
+ # a binary String, or written to a path or IO), render_as_images and monitor_progress_with (a block);
9
+ # read_eml/read_mht (e-mail messages, saved web pages) as read_html. get_document and layout (the
10
+ # DOM API, v42 engine) return com.pd4ml.dom objects, whose lists come back as Ruby Arrays.
9
11
  #
10
12
  # pd = Pd4ml::PD4ML.new
11
13
  # pd.set_page_size(Pd4ml::PageSize::A4)
@@ -117,6 +119,24 @@ module Pd4ml
117
119
  end
118
120
  alias readHtmlString read_html_string
119
121
 
122
+ # Reads an e-mail message (.eml) to convert: its body, HTML if it has one, with the images it
123
+ # embeds, after a block with From, Date, To, Cc, Subject and the attachments, which are
124
+ # embedded in the PDF. `source` as for read_html: a path or URL, or the message itself (a
125
+ # binary String or IO). Of resources outside the message only https:// ones are loaded;
126
+ # add_allowed_resource_location replaces that. read_html recognizes messages too; this is the
127
+ # way for mail from outside your control. PD4ML 4.2 and later.
128
+ def read_eml(source)
129
+ read_mime('readEML', source)
130
+ end
131
+ alias readEML read_eml
132
+
133
+ # Reads a web page saved as a single file (.mht, .mhtml), with the images, style sheets and
134
+ # fonts saved in it; `source` as for read_eml. PD4ML 4.2 and later.
135
+ def read_mht(source)
136
+ read_mime('readMHT', source)
137
+ end
138
+ alias readMHT read_mht
139
+
120
140
  # -------------------------------------------------------------------------------- output
121
141
 
122
142
  # Writes the PDF: to `target` (a path or an IO), or returned as a binary String. spec: a
@@ -175,6 +195,16 @@ module Pd4ml
175
195
 
176
196
  private
177
197
 
198
+ def read_mime(method, source)
199
+ arg = if location?(source)
200
+ Pd4ml.to_url(source)
201
+ else
202
+ source.is_a?(JavaObject) ? source : Pd4ml.binary(source)
203
+ end
204
+ Bridge.get.call(@java, method, [arg])
205
+ nil
206
+ end
207
+
178
208
  def location?(source)
179
209
  source.is_a?(Pathname) || source.is_a?(URI::Generic) ||
180
210
  (source.is_a?(String) && source.encoding != Encoding::BINARY && !source.lstrip.start_with?('<')) ||
data/lib/pd4ml/version.rb CHANGED
@@ -1,5 +1,5 @@
1
- # frozen_string_literal: true
2
-
3
- module Pd4ml
4
- VERSION = '1.0.1'
5
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Pd4ml
4
+ VERSION = '1.1.2'
5
+ end
data/lib/pd4ml.rb CHANGED
@@ -35,6 +35,19 @@ module Pd4ml
35
35
  ResourceProvider = Java.type('com.pd4ml.ResourceProvider')
36
36
  CustomTag = Java.type('com.pd4ml.CustomTag')
37
37
 
38
+ # The DOM API (com.pd4ml.dom, PD4ML 4.2, v42 engine; DMS and UA editions): PD4ML#get_document and
39
+ # PD4ML#layout return its objects. Their Java lists and sets come back as Ruby Arrays, maps as Hashes.
40
+ module Dom
41
+ Node = Java.type('com.pd4ml.dom.Node')
42
+ Document = Java.type('com.pd4ml.dom.Document')
43
+ Element = Java.type('com.pd4ml.dom.Element')
44
+ Text = Java.type('com.pd4ml.dom.Text')
45
+ Comment = Java.type('com.pd4ml.dom.Comment')
46
+ DocumentType = Java.type('com.pd4ml.dom.DocumentType')
47
+ Layout = Java.type('com.pd4ml.dom.Layout')
48
+ LayoutBox = Java.type('com.pd4ml.dom.LayoutBox')
49
+ end
50
+
38
51
  FORMATS = %w[PDF PDFA PDFUA].freeze
39
52
 
40
53
  class << self
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pd4ml
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - PD4ML