diff --git a/Cargo.lock b/Cargo.lock index e4adeed..a7953aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1009,8 +1009,6 @@ dependencies = [ [[package]] name = "genpdfi" version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f5529711a3864fa5ca32a6bf292af9cee2975f9e976e8a1aa11f866d96b43be" dependencies = [ "derive_more 0.99.20", "image 0.24.9", diff --git a/Cargo.toml b/Cargo.toml index 5292f70..7a497c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,3 +74,4 @@ panic = "abort" # pins 0.7 -- see vendor/README.md. [patch.crates-io] printpdf = { path = "vendor/printpdf" } +genpdfi = { path = "vendor/genpdfi" } diff --git a/src/export.rs b/src/export.rs index 928b318..b85a94d 100644 --- a/src/export.rs +++ b/src/export.rs @@ -527,14 +527,6 @@ fn load_pdf_font( .into()) } -/// What the panel corners are painted with. -/// -/// genpdfi rejects images carrying an alpha channel, so a rounded panel cannot -/// be transparent outside its arc -- the corners have to be filled with -/// whatever sits behind them. PDF pages have no background of their own here, -/// so that is the paper. If PDF export grows a theme, this moves with it. -const PAGE_BACKGROUND: [u8; 3] = [255, 255, 255]; - const HEADING_SIZES: [u8; 6] = [24, 20, 16, 14, 12, 11]; /// Width of the A4 content area in mm (210mm page, 15mm side margins). @@ -650,9 +642,7 @@ impl Element for FilledElement { total_width, total_height, self.corner_radius, - context, area, - style, ); result.size.width = total_width; @@ -662,165 +652,65 @@ impl Element for FilledElement { } } -/// Fill a square-cornered rectangle with one stroked line. +/// Fill a panel behind content, with optionally rounded corners. /// -/// A horizontal line of thickness `h` with the PDF default butt cap paints -/// exactly its bounding box, so this is pixel-exact -- and it costs one -/// operator against a PNG encode, a temp file and an embedded XObject. -fn draw_square_background( - color: style::Color, - width: Mm, - height: Mm, - area: genpdfi::render::Area<'_>, -) { - let mid_y = height / 2.0; - area.draw_line( - vec![ - Position::new(Mm::from(0), mid_y), - Position::new(width, mid_y), - ], - style::LineStyle::new() - .with_thickness(height) - .with_color(color), - ); -} - -/// Draw a filled background behind content. Rounded corners need a raster -/// image; square ones are drawn directly. -#[cfg(feature = "images")] +/// This used to rasterize a PNG through a temp file for every panel: a flat +/// colour drawn at 144 DPI, with corners hard-thresholded into visible steps +/// that viewers then smooth-scaled. As a path it is exact at any zoom, needs +/// no temp file and no image XObject, and -- since genpdfi rejects images +/// carrying alpha -- no longer has to paint its corners opaque to fake +/// transparency against the page. fn draw_filled_background( color: style::Color, total_width: Mm, total_height: Mm, corner_radius: f32, - context: &genpdfi::Context, area: genpdfi::render::Area<'_>, - style: style::Style, ) { - if corner_radius <= 0.0 { - draw_square_background(color, total_width, total_height, area); + let (w, h): (f32, f32) = (total_width.into(), total_height.into()); + let r = corner_radius.min(w / 2.0).min(h / 2.0).max(0.0); + let at = |x: f32, y: f32| Position::new(Mm::from(x), Mm::from(y)); + + if r <= 0.0 { + area.fill_polygon( + [ + (at(0.0, 0.0), false), + (at(w, 0.0), false), + (at(w, h), false), + (at(0.0, h), false), + ], + color, + ); return; } - let w_f32: f32 = total_width.into(); - let h_f32: f32 = total_height.into(); - if !render_rounded_bg_on_area(w_f32, h_f32, corner_radius, color, context, area, style) { - // Image rendering failed and the area is consumed, so no background is - // drawn. Only reachable if the temp dir is unwritable. - } -} - -#[cfg(not(feature = "images"))] -fn draw_filled_background( - color: style::Color, - total_width: Mm, - total_height: Mm, - _corner_radius: f32, - _context: &genpdfi::Context, - area: genpdfi::render::Area<'_>, - _style: style::Style, -) { - draw_square_background(color, total_width, total_height, area); -} -/// Create a rounded-rect background image, save to temp file, load via genpdfi, -/// and render it on the given area. Returns true on success. -/// Uses temp file to bridge image 0.25 (our crate) → image 0.24 (genpdfi's crate). -#[cfg(feature = "images")] -fn render_rounded_bg_on_area( - width_mm: f32, - height_mm: f32, - radius_mm: f32, - color: style::Color, - context: &genpdfi::Context, - area: genpdfi::render::Area<'_>, - pdf_style: style::Style, -) -> bool { - let dpi = 144.0_f32; - let px_w = (width_mm * dpi / 25.4).round().max(1.0) as u32; - let px_h = (height_mm * dpi / 25.4).round().max(1.0) as u32; - // Clamp radius so it never exceeds half the smaller dimension - let r = ((radius_mm * dpi / 25.4).round() as u32) - .min(px_w / 2) - .min(px_h / 2); - - let (cr, cg, cb) = match color { - style::Color::Rgb(r, g, b) => (r, g, b), - _ => (243, 244, 248), - }; - - let mut img = image::RgbImage::from_pixel(px_w, px_h, image::Rgb(PAGE_BACKGROUND)); - - let r = r as f32; - let (cx_left, cx_right) = (r, px_w as f32 - r); - let (cy_top, cy_bottom) = (r, px_h as f32 - r); - for y in 0..px_h { - for x in 0..px_w { - let (fx, fy) = (x as f32 + 0.5, y as f32 + 0.5); - // Distance from the arc centre of whichever corner this pixel sits - // in; a pixel outside every corner box is solid panel. - let dx = if fx < cx_left { - cx_left - fx - } else if fx > cx_right { - fx - cx_right - } else { - 0.0 - }; - let dy = if fy < cy_top { - cy_top - fy - } else if fy > cy_bottom { - fy - cy_bottom - } else { - 0.0 - }; - // Blend across the last pixel of the arc. The old hard threshold - // quantised the curve into visible steps, which the viewer then - // smooth-scaled -- soft and jagged at once. - let coverage = if dx == 0.0 || dy == 0.0 { - 1.0 - } else { - (r + 0.5 - (dx * dx + dy * dy).sqrt()).clamp(0.0, 1.0) - }; - if coverage <= 0.0 { - continue; - } - let blend = |ground: u8, fill: u8| { - (ground as f32 + (fill as f32 - ground as f32) * coverage).round() as u8 - }; - img.put_pixel( - x, - y, - image::Rgb([ - blend(PAGE_BACKGROUND[0], cr), - blend(PAGE_BACKGROUND[1], cg), - blend(PAGE_BACKGROUND[2], cb), - ]), - ); - } - } - - // Save as PNG (lossless — avoids JPEG compression artifacts on solid colors). - // Use PID in filename to avoid race conditions with concurrent exports. - // One name per panel: a PID-only name is shared by every panel in the - // document, so two threads would delete each other's file mid-render and - // the background would silently vanish. - static PANEL_SEQ: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); - let seq = PANEL_SEQ.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - let temp_path = - std::env::temp_dir().join(format!("md-pdf-code-bg-{}-{}.png", std::process::id(), seq)); - if img.save(&temp_path).is_err() { - return false; - } - - let ok = if let Ok(mut bg_element) = elements::Image::from_path(&temp_path) { - bg_element.set_dpi(dpi); - let _ = bg_element.render(context, area, pdf_style); - true - } else { - false - }; - - let _ = std::fs::remove_file(&temp_path); - ok + // Distance from an arc's endpoint to its control point for a circular + // quarter-arc in cubic Bézier form. + let k = r * 0.552_284_8; + // printpdf reads a curve as four consecutive entries, and takes the pair + // marked true as its start and first control point. + area.fill_polygon( + [ + (at(r, 0.0), false), + (at(w - r, 0.0), true), + (at(w - r + k, 0.0), true), + (at(w, r - k), false), + (at(w, r), false), + (at(w, h - r), true), + (at(w, h - r + k), true), + (at(w - r + k, h), false), + (at(w - r, h), false), + (at(r, h), true), + (at(r - k, h), true), + (at(0.0, h - r + k), false), + (at(0.0, h - r), false), + (at(0.0, r), true), + (at(0.0, r - k), true), + (at(r - k, 0.0), false), + (at(r, 0.0), false), + ], + color, + ); } /// Walk the AST to find the first H1 heading's text for use as document title. diff --git a/tests/basic_test.rs b/tests/basic_test.rs index aa888c5..81574d7 100644 --- a/tests/basic_test.rs +++ b/tests/basic_test.rs @@ -1950,9 +1950,11 @@ fn test_export_pdf_handles_non_ascii() { assert_eq!(&bytes[0..4], b"%PDF"); } -/// Code blocks and tables are rasterized, so they exercise the image path. +/// Code block and table panels are drawn as filled paths. They used to be +/// rasterized: a flat colour PNG per panel, written to a temp file, decoded and +/// embedded as an image XObject. #[test] -fn test_export_pdf_renders_code_and_tables() { +fn test_export_pdf_panels_are_not_rasterized() { let Some(bytes) = export_pdf( "codetable", "```rust\nfn main() {}\n```\n\n| a | b |\n|---|---|\n| 1 | 2 |\n", @@ -1960,9 +1962,10 @@ fn test_export_pdf_renders_code_and_tables() { return; }; assert!( - bytes.windows(14).any(|w| w.starts_with(b"/Subtype/Image")), - "code block and table should render" + !bytes.windows(14).any(|w| w.starts_with(b"/Subtype/Image")), + "code blocks and tables should embed no images" ); + assert!(bytes.len() > 1000, "the page should still have content"); } /// Every .bold() call in export.rs -- inline bold, headings, table headers, diff --git a/vendor/README.md b/vendor/README.md index 27a2f4e..85735bc 100644 --- a/vendor/README.md +++ b/vendor/README.md @@ -46,3 +46,30 @@ then be deleted outright. Only the files reachable from the build are kept: `src/`, `LICENSE`, and the four assets referenced by `include_str!`/`include_bytes!`. + +# vendor/genpdfi + +genpdfi 0.2.7, unmodified except for one added primitive. Apache-2.0 OR MIT — +see `genpdfi/LICENSES/`. + +`Area` exposed `draw_line`, a *stroked* polyline, and nothing that fills a +path. `Area.layer` is private with no accessor and `Context` carries only the +font cache, so an `Element` had no way to reach printpdf's drawing API — which +does support filled Bézier paths. That left mdx rasterizing every code block +and table background: a flat-colour PNG per panel, written to a temp file, +decoded and embedded as an image XObject. + +Added `Area::fill_polygon` plus the `Layer::add_filled_shape` it calls, both +modelled directly on the existing `draw_line`/`add_line_shape` pair and going +through the same private `position()`/`transform_position()` mapping. They emit +`printpdf::Polygon { mode: PaintMode::Fill }`. + +## Why vendored rather than upstreamed + +genpdfi's repository is **archived** — no issues, no releases. There is +nowhere to send this. + +Revisit if a maintained genpdf-family crate appears; `fill_polygon` is a +generic addition that would be worth offering upstream if one does. + +Only `src/`, `Cargo.toml`, `README.md` and `LICENSES/` are kept. diff --git a/vendor/genpdfi/Cargo.toml b/vendor/genpdfi/Cargo.toml new file mode 100644 index 0000000..8ef2eb4 --- /dev/null +++ b/vendor/genpdfi/Cargo.toml @@ -0,0 +1,53 @@ +[package] +name = "genpdfi" +version = "0.2.7" +authors = ["Robin Krahl ", "Ismael Sh "] +edition = "2018" +description = "User-friendly PDF generator written in pure Rust" +homepage = "https://github.com/theiskaa/genpdfi" +repository = "https://github.com/theiskaa/genpdfi" +keywords = ["pdf", "text", "layout"] +categories = ["text-processing"] +license = "Apache-2.0 OR MIT" +exclude = [".builds/*"] +readme = "README.md" + +[dependencies] +lopdf = "0.26" +rusttype = "0.8" +subsetter = "0.2.3" +ttf-parser = "0.24" + +[dependencies.image] +version = "0.24.9" +default-features = false +optional = true + +[dependencies.hyphenation] +version = "0.8" +optional = true + +[dependencies.printpdf] +version = "0.7.0" +default-features = false + +[dependencies.derive_more] +version = "0.99" +default-features = false +features = ["add", "add_assign", "from", "into", "mul", "mul_assign", "sum"] + +[dev-dependencies.float-cmp] +version = "0.8" +default-features = false +features = ["std"] + +[dev-dependencies.hyphenation] +version = "0.8" +features = ["embed_en-us"] + +[features] +default = [] +images = ["image", "printpdf/embedded_images"] + +[package.metadata.docs.rs] +all-features = true diff --git a/vendor/genpdfi/LICENSES/Apache-2.0.txt b/vendor/genpdfi/LICENSES/Apache-2.0.txt new file mode 100644 index 0000000..4ed90b9 --- /dev/null +++ b/vendor/genpdfi/LICENSES/Apache-2.0.txt @@ -0,0 +1,208 @@ +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, +AND DISTRIBUTION + + 1. Definitions. + + + +"License" shall mean the terms and conditions for use, reproduction, and distribution +as defined by Sections 1 through 9 of this document. + + + +"Licensor" shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + + + +"Legal Entity" shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, "control" means (i) the power, direct +or indirect, to cause the direction or management of such entity, whether +by contract or otherwise, or (ii) ownership of fifty percent (50%) or more +of the outstanding shares, or (iii) beneficial ownership of such entity. + + + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions +granted by this License. + + + +"Source" form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + + + +"Object" form shall mean any form resulting from mechanical transformation +or translation of a Source form, including but not limited to compiled object +code, generated documentation, and conversions to other media types. + + + +"Work" shall mean the work of authorship, whether in Source or Object form, +made available under the License, as indicated by a copyright notice that +is included in or attached to the work (an example is provided in the Appendix +below). + + + +"Derivative Works" shall mean any work, whether in Source or Object form, +that is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative +Works shall not include works that remain separable from, or merely link (or +bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + +"Contribution" shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative +Works thereof, that is intentionally submitted to Licensor for inclusion in +the Work by the copyright owner or by an individual or Legal Entity authorized +to submit on behalf of the copyright owner. For the purposes of this definition, +"submitted" means any form of electronic, verbal, or written communication +sent to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor +for the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as "Not a Contribution." + + + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently incorporated +within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this +License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable copyright license to reproduce, prepare +Derivative Works of, publicly display, publicly perform, sublicense, and distribute +the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, +each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) patent +license to make, have made, use, offer to sell, sell, import, and otherwise +transfer the Work, where such license applies only to those patent claims +licensable by such Contributor that are necessarily infringed by their Contribution(s) +alone or by combination of their Contribution(s) with the Work to which such +Contribution(s) was submitted. If You institute patent litigation against +any entity (including a cross-claim or counterclaim in a lawsuit) alleging +that the Work or a Contribution incorporated within the Work constitutes direct +or contributory patent infringement, then any patent licenses granted to You +under this License for that Work shall terminate as of the date such litigation +is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or +Derivative Works thereof in any medium, with or without modifications, and +in Source or Object form, provided that You meet the following conditions: + +(a) You must give any other recipients of the Work or Derivative Works a copy +of this License; and + +(b) You must cause any modified files to carry prominent notices stating that +You changed the files; and + +(c) You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source +form of the Work, excluding those notices that do not pertain to any part +of the Derivative Works; and + +(d) If the Work includes a "NOTICE" text file as part of its distribution, +then any Derivative Works that You distribute must include a readable copy +of the attribution notices contained within such NOTICE file, excluding those +notices that do not pertain to any part of the Derivative Works, in at least +one of the following places: within a NOTICE text file distributed as part +of the Derivative Works; within the Source form or documentation, if provided +along with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents +of the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works +that You distribute, alongside or as an addendum to the NOTICE text from the +Work, provided that such additional attribution notices cannot be construed +as modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, +or distribution of Your modifications, or for any such Derivative Works as +a whole, provided Your use, reproduction, and distribution of the Work otherwise +complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any +Contribution intentionally submitted for inclusion in the Work by You to the +Licensor shall be under the terms and conditions of this License, without +any additional terms or conditions. Notwithstanding the above, nothing herein +shall supersede or modify the terms of any separate license agreement you +may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, +trademarks, service marks, or product names of the Licensor, except as required +for reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to +in writing, Licensor provides the Work (and each Contributor provides its +Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied, including, without limitation, any warranties +or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR +A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness +of using or redistributing the Work and assume any risks associated with Your +exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether +in tort (including negligence), contract, or otherwise, unless required by +applicable law (such as deliberate and grossly negligent acts) or agreed to +in writing, shall any Contributor be liable to You for damages, including +any direct, indirect, special, incidental, or consequential damages of any +character arising as a result of this License or out of the use or inability +to use the Work (including but not limited to damages for loss of goodwill, +work stoppage, computer failure or malfunction, or any and all other commercial +damages or losses), even if such Contributor has been advised of the possibility +of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work +or Derivative Works thereof, You may choose to offer, and charge a fee for, +acceptance of support, warranty, indemnity, or other liability obligations +and/or rights consistent with this License. However, in accepting such obligations, +You may act only on Your own behalf and on Your sole responsibility, not on +behalf of any other Contributor, and only if You agree to indemnify, defend, +and hold each Contributor harmless for any liability incurred by, or claims +asserted against, such Contributor by reason of your accepting any such warranty +or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets "[]" replaced with your own identifying +information. (Don't include the brackets!) The text should be enclosed in +the appropriate comment syntax for the file format. We also recommend that +a file or class name and description of purpose be included on the same "printed +page" as the copyright notice for easier identification within third-party +archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. diff --git a/vendor/genpdfi/LICENSES/CC0-1.0.txt b/vendor/genpdfi/LICENSES/CC0-1.0.txt new file mode 100644 index 0000000..a343ccd --- /dev/null +++ b/vendor/genpdfi/LICENSES/CC0-1.0.txt @@ -0,0 +1,119 @@ +Creative Commons Legal Code + +CC0 1.0 Universal CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES +NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE +AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION +ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE +OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS +LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION +OR WORKS PROVIDED HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer exclusive +Copyright and Related Rights (defined below) upon the creator and subsequent +owner(s) (each and all, an "owner") of an original work of authorship and/or +a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for the +purpose of contributing to a commons of creative, cultural and scientific +works ("Commons") that the public can reliably and without fear of later claims +of infringement build upon, modify, incorporate in other works, reuse and +redistribute as freely as possible in any form whatsoever and for any purposes, +including without limitation commercial purposes. These owners may contribute +to the Commons to promote the ideal of a free culture and the further production +of creative, cultural and scientific works, or to gain reputation or greater +distribution for their Work in part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any expectation +of additional consideration or compensation, the person associating CC0 with +a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright +and Related Rights in the Work, voluntarily elects to apply CC0 to the Work +and publicly distribute the Work under its terms, with knowledge of his or +her Copyright and Related Rights in the Work and the meaning and intended +legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be protected +by copyright and related or neighboring rights ("Copyright and Related Rights"). +Copyright and Related Rights include, but are not limited to, the following: + +i. the right to reproduce, adapt, distribute, perform, display, communicate, +and translate a Work; + + ii. moral rights retained by the original author(s) and/or performer(s); + +iii. publicity and privacy rights pertaining to a person's image or likeness +depicted in a Work; + +iv. rights protecting against unfair competition in regards to a Work, subject +to the limitations in paragraph 4(a), below; + +v. rights protecting the extraction, dissemination, use and reuse of data +in a Work; + +vi. database rights (such as those arising under Directive 96/9/EC of the +European Parliament and of the Council of 11 March 1996 on the legal protection +of databases, and under any national implementation thereof, including any +amended or successor version of such directive); and + +vii. other similar, equivalent or corresponding rights throughout the world +based on applicable law or treaty, and any national implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention of, +applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and +unconditionally waives, abandons, and surrenders all of Affirmer's Copyright +and Related Rights and associated claims and causes of action, whether now +known or unknown (including existing as well as future claims and causes of +action), in the Work (i) in all territories worldwide, (ii) for the maximum +duration provided by applicable law or treaty (including future time extensions), +(iii) in any current or future medium and for any number of copies, and (iv) +for any purpose whatsoever, including without limitation commercial, advertising +or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the +benefit of each member of the public at large and to the detriment of Affirmer's +heirs and successors, fully intending that such Waiver shall not be subject +to revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason be +judged legally invalid or ineffective under applicable law, then the Waiver +shall be preserved to the maximum extent permitted taking into account Affirmer's +express Statement of Purpose. In addition, to the extent the Waiver is so +judged Affirmer hereby grants to each affected person a royalty-free, non +transferable, non sublicensable, non exclusive, irrevocable and unconditional +license to exercise Affirmer's Copyright and Related Rights in the Work (i) +in all territories worldwide, (ii) for the maximum duration provided by applicable +law or treaty (including future time extensions), (iii) in any current or +future medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional purposes +(the "License"). The License shall be deemed effective as of the date CC0 +was applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder of +the License, and in such case Affirmer hereby affirms that he or she will +not (i) exercise any of his or her remaining Copyright and Related Rights +in the Work or (ii) assert any associated claims and causes of action with +respect to the Work, in either case contrary to Affirmer's express Statement +of Purpose. + + 4. Limitations and Disclaimers. + +a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, +licensed or otherwise affected by this document. + +b. Affirmer offers the Work as-is and makes no representations or warranties +of any kind concerning the Work, express, implied, statutory or otherwise, +including without limitation warranties of title, merchantability, fitness +for a particular purpose, non infringement, or the absence of latent or other +defects, accuracy, or the present or absence of errors, whether or not discoverable, +all to the greatest extent permissible under applicable law. + +c. Affirmer disclaims responsibility for clearing rights of other persons +that may apply to the Work or any use thereof, including without limitation +any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims +responsibility for obtaining any necessary consents, permissions or other +rights required for any use of the Work. + +d. Affirmer understands and acknowledges that Creative Commons is not a party +to this document and has no duty or obligation with respect to this CC0 or +use of the Work. diff --git a/vendor/genpdfi/LICENSES/MIT.txt b/vendor/genpdfi/LICENSES/MIT.txt new file mode 100644 index 0000000..204b93d --- /dev/null +++ b/vendor/genpdfi/LICENSES/MIT.txt @@ -0,0 +1,19 @@ +MIT License Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice (including the next +paragraph) shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/genpdfi/README.md b/vendor/genpdfi/README.md new file mode 100644 index 0000000..33131cc --- /dev/null +++ b/vendor/genpdfi/README.md @@ -0,0 +1,4 @@ +# genpdfi (improved) + +A fork of https://docs.rs/genpdf/latest/genpdf/ but improved. +Mainly used in [markdown2pdf](https://github.com/theiskaa/markdown2pdf) library. diff --git a/vendor/genpdfi/src/elements.rs b/vendor/genpdfi/src/elements.rs new file mode 100644 index 0000000..7cbc37e --- /dev/null +++ b/vendor/genpdfi/src/elements.rs @@ -0,0 +1,1530 @@ +//! Elements of a PDF document. +//! +//! This module provides implementations of the [`Element`][] trait that can be used to render and +//! arrange text and shapes. +//! +//! It includes the following elements: +//! - Containers: +//! - [`LinearLayout`][]: arranges its elements sequentially +//! - [`TableLayout`][]: arranges its elements in columns and rows +//! - [`OrderedList`][] and [`UnorderedList`][]: arrange their elements sequentially with bullet +//! points +//! - Text: +//! - [`Text`][]: a single line of text +//! - [`Paragraph`][]: a wrapped and aligned paragraph of text +//! - Wrappers: +//! - [`FramedElement`][]: draws a frame around the wrapped element +//! - [`PaddedElement`][]: adds a padding to the wrapped element +//! - [`StyledElement`][]: sets a default style for the wrapped element and its children +//! - Other: +//! - [`Image`][]: an image (requires the `images` feature) +//! - [`Break`][]: adds forced line breaks as a spacer +//! - [`PageBreak`][]: adds a forced page break +//! +//! You can create custom elements by implementing the [`Element`][] trait. +//! +//! [`Element`]: ../trait.Element.html +//! [`LinearLayout`]: struct.LinearLayout.html +//! [`TableLayout`]: struct.TableLayout.html +//! [`OrderedList`]: struct.OrderedList.html +//! [`UnorderedList`]: struct.UnorderedList.html +//! [`Text`]: struct.Text.html +//! [`Image`]: struct.Image.html +//! [`Break`]: struct.Break.html +//! [`PageBreak`]: struct.PageBreak.html +//! [`Paragraph`]: struct.Paragraph.html +//! [`FramedElement`]: struct.FramedElement.html +//! [`PaddedElement`]: struct.PaddedElement.html +//! [`StyledElement`]: struct.StyledElement.html + +#[cfg(feature = "images")] +mod images; + +use std::collections; +use std::iter; +use std::mem; + +use crate::error::{Error, ErrorKind}; +use crate::fonts; +use crate::render; +use crate::style; +use crate::style::{LineStyle, Style, StyledString}; +use crate::wrap; +use crate::{Alignment, Context, Element, Margins, Mm, Position, RenderResult, Size}; + +#[cfg(feature = "images")] +pub use images::Image; + +/// Helper trait for creating boxed elements. +pub trait IntoBoxedElement { + /// Creates a boxed element from this element. + fn into_boxed_element(self) -> Box; +} + +impl IntoBoxedElement for E { + fn into_boxed_element(self) -> Box { + Box::new(self) + } +} + +impl IntoBoxedElement for Box { + fn into_boxed_element(self) -> Box { + self + } +} + +/// Arranges a list of elements sequentially. +/// +/// Currently, elements can only be arranged vertically. +/// +/// # Examples +/// +/// With setters: +/// ``` +/// use genpdfi::elements; +/// let mut layout = elements::LinearLayout::vertical(); +/// layout.push(elements::Paragraph::new("Test1")); +/// layout.push(elements::Paragraph::new("Test2")); +/// ``` +/// +/// Chained: +/// ``` +/// use genpdfi::elements; +/// let layout = elements::LinearLayout::vertical() +/// .element(elements::Paragraph::new("Test1")) +/// .element(elements::Paragraph::new("Test2")); +/// ``` +/// +pub struct LinearLayout { + elements: Vec>, + render_idx: usize, +} + +impl LinearLayout { + fn new() -> LinearLayout { + LinearLayout { + elements: Vec::new(), + render_idx: 0, + } + } + + /// Creates a new linear layout that arranges its elements vertically. + pub fn vertical() -> LinearLayout { + LinearLayout::new() + } + + /// Adds the given element to this layout. + pub fn push(&mut self, element: E) { + self.elements.push(element.into_boxed_element()); + } + + /// Adds the given element to this layout and it returns the layout. + pub fn element(mut self, element: E) -> Self { + self.push(element); + self + } + + fn render_vertical( + &mut self, + context: &Context, + mut area: render::Area<'_>, + style: Style, + ) -> Result { + let mut result = RenderResult::default(); + while area.size().height > Mm(0.0) && self.render_idx < self.elements.len() { + let element_result = + self.elements[self.render_idx].render(context, area.clone(), style)?; + area.add_offset(Position::new(0, element_result.size.height)); + result.size = result.size.stack_vertical(element_result.size); + if element_result.has_more { + result.has_more = true; + return Ok(result); + } + self.render_idx += 1; + } + result.has_more = self.render_idx < self.elements.len(); + Ok(result) + } +} + +impl Element for LinearLayout { + fn render( + &mut self, + context: &Context, + area: render::Area<'_>, + style: Style, + ) -> Result { + // TODO: add horizontal layout + self.render_vertical(context, area, style) + } +} + +impl iter::Extend for LinearLayout { + fn extend>(&mut self, iter: I) { + self.elements + .extend(iter.into_iter().map(|e| e.into_boxed_element())) + } +} + +/// A single line of formatted text. +/// +/// This element renders a single styled string on a single line. It does not wrap it if the +/// string is longer than the line. Therefore you should prefer [`Paragraph`][] over `Text` for +/// most use cases. +/// +/// [`Paragraph`]: struct.Paragraph.html +#[derive(Clone, Debug, Default)] +pub struct Text { + text: StyledString, +} + +impl Text { + /// Creates a new instance with the given styled string. + pub fn new(text: impl Into) -> Text { + Text { text: text.into() } + } +} + +impl Element for Text { + fn render( + &mut self, + context: &Context, + area: render::Area<'_>, + mut style: Style, + ) -> Result { + let mut result = RenderResult::default(); + style.merge(self.text.style); + if area.print_str( + &context.font_cache, + Position::default(), + style, + &self.text.s, + )? { + result.size = Size::new( + style.str_width(&context.font_cache, &self.text.s), + style.line_height(&context.font_cache), + ); + } else { + result.has_more = true; + } + Ok(result) + } +} + +/// A multi-line wrapped paragraph of formatted text. +/// +/// If the text of this paragraph is longer than the page width, the paragraph is wrapped at word +/// borders (and additionally at string borders if it contains multiple strings). If a word in the +/// paragraph is longer than the page width, the text is truncated. +/// +/// Use the [`push`][], [`string`][], [`push_styled`][] and [`string_styled`][] methods to add +/// strings to this paragraph. Besides the styling of the text (see [`Style`][]), you can also set +/// an [`Alignment`][] for the paragraph. +/// +/// The line height and spacing are calculated based on the style of each string. +/// +/// # Examples +/// +/// With setters: +/// ``` +/// use genpdfi::{elements, style}; +/// let mut p = elements::Paragraph::default(); +/// p.push("This is an "); +/// p.push_styled("important", style::Color::Rgb(255, 0, 0)); +/// p.push(" message!"); +/// p.set_alignment(genpdfi::Alignment::Center); +/// ``` +/// +/// Chained: +/// ``` +/// use genpdfi::{elements, style}; +/// let p = elements::Paragraph::default() +/// .string("This is an ") +/// .styled_string("important", style::Color::Rgb(255, 0, 0)) +/// .string(" message!") +/// .aligned(genpdfi::Alignment::Center); +/// ``` +/// +/// [`Style`]: ../style/struct.Style.html +/// [`Alignment`]: ../enum.Alignment.html +/// [`Element::styled`]: ../trait.Element.html#method.styled +/// [`push`]: #method.push +/// [`push_styled`]: #method.push_styled +/// [`string`]: #method.string +/// [`string_styled`]: #method.string_styled +#[derive(Clone, Debug, Default)] +pub struct Paragraph { + text: Vec, + words: collections::VecDeque, + style_applied: bool, + alignment: Alignment, +} + +impl Paragraph { + /// Creates a new paragraph with the given content. + pub fn new(text: impl Into) -> Paragraph { + Paragraph { + text: vec![text.into()], + ..Default::default() + } + } + + /// Sets the alignment of this paragraph. + pub fn set_alignment(&mut self, alignment: Alignment) { + self.alignment = alignment; + } + + /// Sets the alignment of this paragraph and returns the paragraph. + pub fn aligned(mut self, alignment: Alignment) -> Self { + self.set_alignment(alignment); + self + } + + /// Adds a string to the end of this paragraph. + pub fn push(&mut self, s: impl Into) { + self.text.push(s.into()); + } + + /// Adds a string to the end of this paragraph and returns the paragraph. + pub fn string(mut self, s: impl Into) -> Self { + self.push(s); + self + } + + /// Adds a string with the given style to the end of this paragraph. + pub fn push_styled(&mut self, s: impl Into, style: impl Into