dynimg 0.1.9__tar.gz → 0.1.10__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. {dynimg-0.1.9 → dynimg-0.1.10}/Cargo.lock +1 -1
  2. {dynimg-0.1.9 → dynimg-0.1.10}/Cargo.toml +2 -2
  3. dynimg-0.1.10/Makefile +34 -0
  4. {dynimg-0.1.9 → dynimg-0.1.10}/PKG-INFO +1 -1
  5. {dynimg-0.1.9 → dynimg-0.1.10}/pyproject.toml +1 -1
  6. {dynimg-0.1.9 → dynimg-0.1.10}/python/dynimg/__init__.py +3 -1
  7. {dynimg-0.1.9 → dynimg-0.1.10}/src/lib.rs +5 -1
  8. {dynimg-0.1.9 → dynimg-0.1.10}/test_wheels/.gitignore +3 -0
  9. {dynimg-0.1.9 → dynimg-0.1.10}/test_wheels/test_dynimg.py +66 -19
  10. {dynimg-0.1.9 → dynimg-0.1.10}/.claude/settings.local.json +0 -0
  11. {dynimg-0.1.9 → dynimg-0.1.10}/.github/workflows/build-wheels.yml +0 -0
  12. {dynimg-0.1.9 → dynimg-0.1.10}/.github/workflows/ci.yml +0 -0
  13. {dynimg-0.1.9 → dynimg-0.1.10}/.github/workflows/release.yml +0 -0
  14. {dynimg-0.1.9 → dynimg-0.1.10}/.gitignore +0 -0
  15. {dynimg-0.1.9 → dynimg-0.1.10}/.pearls/CLAUDE.md +0 -0
  16. {dynimg-0.1.9 → dynimg-0.1.10}/.pearls/issues.jsonl +0 -0
  17. {dynimg-0.1.9 → dynimg-0.1.10}/CLAUDE.md +0 -0
  18. {dynimg-0.1.9 → dynimg-0.1.10}/LICENSE +0 -0
  19. {dynimg-0.1.9 → dynimg-0.1.10}/README.md +0 -0
  20. {dynimg-0.1.9 → dynimg-0.1.10}/codebook.toml +0 -0
  21. {dynimg-0.1.9 → dynimg-0.1.10}/examples/assets/PlaywriteINGuides-Regular.ttf +0 -0
  22. {dynimg-0.1.9 → dynimg-0.1.10}/examples/assets/RobotoMono-Bold.ttf +0 -0
  23. {dynimg-0.1.9 → dynimg-0.1.10}/examples/assets/RobotoMono-Bold.woff2 +0 -0
  24. {dynimg-0.1.9 → dynimg-0.1.10}/examples/assets/logo.svg +0 -0
  25. {dynimg-0.1.9 → dynimg-0.1.10}/examples/assets/servo.css +0 -0
  26. {dynimg-0.1.9 → dynimg-0.1.10}/examples/assets/style.css +0 -0
  27. {dynimg-0.1.9 → dynimg-0.1.10}/examples/google-fonts.html +0 -0
  28. {dynimg-0.1.9 → dynimg-0.1.10}/examples/inline-only.html +0 -0
  29. {dynimg-0.1.9 → dynimg-0.1.10}/examples/local-assets.html +0 -0
  30. {dynimg-0.1.9 → dynimg-0.1.10}/examples/local-font-woff2.html +0 -0
  31. {dynimg-0.1.9 → dynimg-0.1.10}/examples/local-font.html +0 -0
  32. {dynimg-0.1.9 → dynimg-0.1.10}/examples/mixed-assets.html +0 -0
  33. {dynimg-0.1.9 → dynimg-0.1.10}/examples/og-image.html +0 -0
  34. {dynimg-0.1.9 → dynimg-0.1.10}/examples/quote.html +0 -0
  35. {dynimg-0.1.9 → dynimg-0.1.10}/examples/remote-image.html +0 -0
  36. {dynimg-0.1.9 → dynimg-0.1.10}/examples/servo.html +0 -0
  37. {dynimg-0.1.9 → dynimg-0.1.10}/examples/social-card.html +0 -0
  38. {dynimg-0.1.9 → dynimg-0.1.10}/python/dynimg/__init__.pyi +0 -0
  39. {dynimg-0.1.9 → dynimg-0.1.10}/python/dynimg/py.typed +0 -0
  40. {dynimg-0.1.9 → dynimg-0.1.10}/scripts/render-examples.sh +0 -0
  41. {dynimg-0.1.9 → dynimg-0.1.10}/src/main.rs +0 -0
  42. {dynimg-0.1.9 → dynimg-0.1.10}/src/python.rs +0 -0
  43. {dynimg-0.1.9 → dynimg-0.1.10}/test_wheels/README.md +0 -0
  44. {dynimg-0.1.9 → dynimg-0.1.10}/test_wheels/debug_parser.py +0 -0
  45. {dynimg-0.1.9 → dynimg-0.1.10}/test_wheels/debug_verbose.py +0 -0
  46. {dynimg-0.1.9 → dynimg-0.1.10}/test_wheels/test_from_ci.sh +0 -0
@@ -630,7 +630,7 @@ dependencies = [
630
630
 
631
631
  [[package]]
632
632
  name = "dynimg"
633
- version = "0.1.9"
633
+ version = "0.1.10"
634
634
  dependencies = [
635
635
  "anyhow",
636
636
  "anyrender",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "dynimg"
3
- version = "0.1.9"
3
+ version = "0.1.10"
4
4
  edition = "2024"
5
5
  description = "A fast library and CLI for rendering HTML/CSS to images"
6
6
  license = "MIT"
@@ -65,4 +65,4 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
65
65
  bytes = "1"
66
66
 
67
67
  [profile.release]
68
- lto = 'thin'
68
+ lto = "thin"
dynimg-0.1.10/Makefile ADDED
@@ -0,0 +1,34 @@
1
+ .PHONY: install dev test lint fmt clean build
2
+
3
+ # Install Python package into local venv
4
+ install:
5
+ maturin develop --release
6
+
7
+ # Install in development mode (faster builds, debug symbols)
8
+ dev:
9
+ maturin develop
10
+
11
+ # Run tests
12
+ test: install
13
+ cargo test
14
+ python test_wheels/test_dynimg.py
15
+
16
+ # Run lints
17
+ lint:
18
+ cargo clippy -- -D warnings
19
+ cargo fmt -- --check
20
+
21
+ # Format code
22
+ fmt:
23
+ cargo fmt
24
+
25
+ # Clean build artifacts
26
+ clean:
27
+ cargo clean
28
+ rm -rf dist/
29
+ rm -rf target/
30
+ rm -rf *.egg-info/
31
+
32
+ # Build release wheels
33
+ build:
34
+ maturin build --release
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dynimg
3
- Version: 0.1.9
3
+ Version: 0.1.10
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "dynimg"
7
- version = "0.1.9"
7
+ version = "0.1.10"
8
8
  description = "A fast library for rendering HTML/CSS to images"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -32,4 +32,6 @@ __all__ = [
32
32
  "render_to_file",
33
33
  ]
34
34
 
35
- __version__ = "0.1.0"
35
+ from importlib.metadata import version as _get_version
36
+
37
+ __version__ = _get_version("dynimg")
@@ -574,6 +574,10 @@ fn write_webp_lossless(path: &Path, buffer: &[u8], width: u32, height: u32) -> R
574
574
 
575
575
  fn encode_webp_lossless(buffer: &[u8], width: u32, height: u32) -> Vec<u8> {
576
576
  let encoder = webp::Encoder::from_rgba(buffer, width, height);
577
- let webp_data = encoder.encode_lossless();
577
+ let mut config = webp::WebPConfig::new().unwrap();
578
+ config.lossless = 1;
579
+ config.quality = 75.0;
580
+ config.method = 0; // 0=fastest, 6=slowest (default)
581
+ let webp_data = encoder.encode_advanced(&config).unwrap();
578
582
  webp_data.to_vec()
579
583
  }
@@ -2,3 +2,6 @@
2
2
  *.whl
3
3
  .venv/
4
4
  test_output.*
5
+ *.jpg
6
+ *.webp
7
+ *.png
@@ -2,6 +2,20 @@
2
2
  """Test script for dynimg wheels."""
3
3
 
4
4
  import sys
5
+ import time
6
+ from contextlib import contextmanager
7
+
8
+
9
+ class RunTimer:
10
+ def __init__(self):
11
+ self.start = time.perf_counter()
12
+
13
+ @contextmanager
14
+ def measure(self, name):
15
+ t0 = time.perf_counter()
16
+ yield
17
+ t1 = time.perf_counter()
18
+ print(f"[{t1 - self.start:.3f}s] {name} took {(t1 - t0) * 1000:.2f}ms")
5
19
 
6
20
 
7
21
  def test_import():
@@ -17,11 +31,26 @@ def test_render_basic():
17
31
  """Test basic rendering."""
18
32
  import dynimg
19
33
 
34
+ timer = RunTimer()
20
35
  html = '<html><body style="background:blue;"><h1>Test</h1></body></html>'
21
- img = dynimg.render(html, dynimg.RenderOptions(width=100, height=100, scale=1.0))
36
+
37
+ with timer.measure("Render"):
38
+ img = dynimg.render(
39
+ html, dynimg.RenderOptions(width=100, height=100, scale=1.0)
40
+ )
22
41
 
23
42
  assert img.width == 100, f"Expected width 100, got {img.width}"
24
43
  assert img.height == 100, f"Expected height 100, got {img.height}"
44
+
45
+ with timer.measure("Save PNG"):
46
+ img.save_png("test_basic.png")
47
+
48
+ with timer.measure("Save JPEG"):
49
+ img.save_jpeg("test_basic.jpg")
50
+
51
+ with timer.measure("Save WebP"):
52
+ img.save_webp("test_basic.webp")
53
+
25
54
  print(f"Basic render: {img.width}x{img.height}")
26
55
  return True
27
56
 
@@ -30,6 +59,7 @@ def test_render_gradient():
30
59
  """Test gradient rendering."""
31
60
  import dynimg
32
61
 
62
+ timer = RunTimer()
33
63
  html = """
34
64
  <html>
35
65
  <body style="background: linear-gradient(135deg, #667eea, #764ba2);
@@ -42,10 +72,21 @@ def test_render_gradient():
42
72
  </html>
43
73
  """
44
74
  options = dynimg.RenderOptions(width=1200, height=630, scale=2.0)
45
- img = dynimg.render(html, options)
75
+
76
+ with timer.measure("Render"):
77
+ img = dynimg.render(html, options)
46
78
 
47
79
  assert img.width == 2400, f"Expected width 2400, got {img.width}"
48
80
  assert img.height == 1260, f"Expected height 1260, got {img.height}"
81
+ with timer.measure("Save WebP"):
82
+ img.save_webp("test_gradient.webp")
83
+
84
+ with timer.measure("Save PNG"):
85
+ img.save_png("test_gradient.png")
86
+
87
+ with timer.measure("Save JPEG"):
88
+ img.save_jpeg("test_gradient.jpg")
89
+
49
90
  print(f"Gradient render: {img.width}x{img.height}")
50
91
  return True
51
92
 
@@ -56,32 +97,32 @@ def test_save_formats():
56
97
 
57
98
  import dynimg
58
99
 
100
+ timer = RunTimer()
59
101
  html = '<html><body style="background:red; width:50px; height:50px;"></body></html>'
60
- img = dynimg.render(html, dynimg.RenderOptions(width=50, height=50, scale=1.0))
102
+ with timer.measure("Render"):
103
+ img = dynimg.render(html, dynimg.RenderOptions(width=50, height=50, scale=1.0))
61
104
 
62
105
  # Test PNG
63
- img.save_png("test_output.png")
106
+ with timer.measure("Save PNG"):
107
+ img.save_png("test_output.png")
64
108
  assert os.path.exists("test_output.png"), "PNG file not created"
65
109
  png_size = os.path.getsize("test_output.png")
66
110
  print(f"PNG saved: {png_size} bytes")
67
111
 
68
112
  # Test WebP
69
- img.save_webp("test_output.webp")
113
+ with timer.measure("Save WebP"):
114
+ img.save_webp("test_output.webp")
70
115
  assert os.path.exists("test_output.webp"), "WebP file not created"
71
116
  webp_size = os.path.getsize("test_output.webp")
72
117
  print(f"WebP saved: {webp_size} bytes")
73
118
 
74
119
  # Test JPEG
75
- img.save_jpeg("test_output.jpg", quality=90)
120
+ with timer.measure("Save JPEG"):
121
+ img.save_jpeg("test_output.jpg", quality=90)
76
122
  assert os.path.exists("test_output.jpg"), "JPEG file not created"
77
123
  jpeg_size = os.path.getsize("test_output.jpg")
78
124
  print(f"JPEG saved: {jpeg_size} bytes")
79
125
 
80
- # Cleanup
81
- os.remove("test_output.png")
82
- os.remove("test_output.webp")
83
- os.remove("test_output.jpg")
84
-
85
126
  return True
86
127
 
87
128
 
@@ -89,10 +130,13 @@ def test_to_bytes():
89
130
  """Test encoding to bytes."""
90
131
  import dynimg
91
132
 
133
+ timer = RunTimer()
92
134
  html = (
93
135
  '<html><body style="background:green; width:50px; height:50px;"></body></html>'
94
136
  )
95
- img = dynimg.render(html, dynimg.RenderOptions(width=50, height=50, scale=1.0))
137
+
138
+ with timer.measure("Render"):
139
+ img = dynimg.render(html, dynimg.RenderOptions(width=50, height=50, scale=1.0))
96
140
 
97
141
  png_bytes = img.to_png()
98
142
  assert len(png_bytes) > 0, "PNG bytes empty"
@@ -118,21 +162,24 @@ def test_render_to_file():
118
162
 
119
163
  import dynimg
120
164
 
165
+ timer = RunTimer()
121
166
  html = (
122
167
  '<html><body style="background:yellow; width:50px; height:50px;"></body></html>'
123
168
  )
124
169
 
125
- dynimg.render_to_file(html, "test_direct.png")
170
+ with timer.measure("render_to_file PNG"):
171
+ dynimg.render_to_file(html, "test_direct.png")
126
172
  assert os.path.exists("test_direct.png"), "Direct PNG not created"
127
173
  print(f"render_to_file PNG: {os.path.getsize('test_direct.png')} bytes")
128
174
  os.remove("test_direct.png")
129
175
 
130
- dynimg.render_to_file(
131
- html,
132
- "test_direct.webp",
133
- options=dynimg.RenderOptions(width=100, height=100, scale=1.0),
134
- quality=85,
135
- )
176
+ with timer.measure("render_to_file WebP"):
177
+ dynimg.render_to_file(
178
+ html,
179
+ "test_direct.webp",
180
+ options=dynimg.RenderOptions(width=100, height=100, scale=1.0),
181
+ quality=85,
182
+ )
136
183
  assert os.path.exists("test_direct.webp"), "Direct WebP not created"
137
184
  print(f"render_to_file WebP: {os.path.getsize('test_direct.webp')} bytes")
138
185
  os.remove("test_direct.webp")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes