release: v2.4.0 — in-memory image API + merged-cell round-trip fix#33
Conversation
- parse w:tcPr in hydrateTableCell to restore horizontal/vertical merges - add TestGridSpanPreservedAfterRoundTrip reproduction test Closes #25 🐛 - Generated by Copilot
- use cell.Merge(span, 1) instead of SetGridSpan to mark continuation cells - compute grid column count from gridSpan sums, not tc element count - track column offset in hydration loop for correct cell-to-grid mapping - use WrapWithContext for gridSpan Atoi error with attr name and value - add TestVMergePreservedAfterRoundTrip for vertical merge coverage - assert IsHorizontallyMergedContinuation on spanned-over cells 🔧 - Generated by Copilot
Add support for adding images from raw byte data without requiring file system access. This addresses the use case where users have limited access to a file system (issue #29). New public API methods on ParagraphBuilder: - AddImageFromBytes(data, format) - AddImageFromBytesWithSize(data, format, size) - AddImageFromBytesWithPosition(data, format, size, pos) New domain.Paragraph interface methods: - AddImageFromBytes(data, format) - AddImageFromBytesWithSize(data, format, size) - AddImageFromBytesWithPosition(data, format, size, pos) New internal constructors: - core.NewImageFromBytes(id, data, format) - core.NewImageFromBytesWithSize(id, data, format, size) - core.NewImageFromBytesWithPosition(id, data, format, size, pos) Includes unit tests and updated example 08_images. Closes #29
…around The CLI handler previously wrote base64 image data to a temporary file and then called AddImage(path). Now it uses AddImageFromBytes directly, eliminating unnecessary disk I/O and temp file cleanup.
- Validate format against known image formats, reject unsupported values - Normalize format (lowercase, trim dots, map jpg->jpeg, tiff->tif) - Extract shared normalizeImageFormat() used by both detectImageFormat and NewImageFromBytes - Fix getImageDimensions to use bytes.NewReader instead of strings.NewReader(string(data)) to avoid unnecessary allocation - Remove double copy in NewImageFromBytes (Data() already returns copy) - Use img.Format() for sourceName in paragraph methods to stay consistent after normalization - Fix error arg: pass data instead of nil in InvalidArgument - Add tests for invalid format rejection and format normalization
- Restore defensive copy of data in NewImageFromBytes to prevent caller mutation (consistent with NewImageFromPackage) - Fix AddImageFromBytesWithPosition to use img.Format() instead of raw format parameter for sourceName consistency - Fix jpg normalization test to use actual JPEG-encoded bytes instead of PNG bytes with JPG format declaration
There was a problem hiding this comment.
Code Review
This pull request updates the library to version 2.4.0, introducing a new in-memory image API that allows inserting images from byte slices without temporary files. It also fixes a critical issue where table cell merge metadata (gridSpan and vMerge) was lost during document round-trips. The CLI handler was updated to utilize the new image API for base64 data, and comprehensive tests were added to verify the fixes and new functionality. Feedback suggests improving type safety for error context maps.
| if val, ok := getAttr(gs, "val"); ok && val != "" { | ||
| span, err := strconv.Atoi(val) | ||
| if err != nil { | ||
| return errors.WrapWithContext(err, opHydrateTableCell, map[string]interface{}{"attr": "gridSpan", "value": val}) |
There was a problem hiding this comment.
Pull request overview
Release v2.4.0 combining two main efforts: (1) adding an in-memory image insertion API (no filesystem required), and (2) fixing DOCX read/write round-tripping so merged table cells preserve w:gridSpan and w:vMerge when reopening documents.
Changes:
- Added
AddImageFromBytes*APIs acrossParagraphBuilder,domain.Paragraph, andinternal/coreconstructors; updated CLI base64 image handling to use the in-memory path. - Fixed table hydration to restore merged-cell metadata (
gridSpan,vMerge) and correctly map XML cells to grid columns. - Added regression/unit tests plus release/docs updates for v2.4.0.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/reader/reconstruct.go | Hydrates gridSpan/vMerge from <w:tcPr> and remaps columns using gridSpan sums. |
| internal/core/paragraph.go | Implements Paragraph.AddImageFromBytes* methods and attaches images via media/relationship managers. |
| internal/core/image.go | Adds byte-based image constructors, format normalization, and fixes dimension detection to use bytes.NewReader. |
| internal/core/image_test.go | Adds unit tests for NewImageFromBytes* including normalization and error cases. |
| builder.go | Exposes builder fluent APIs AddImageFromBytes*. |
| domain/paragraph.go | Extends the public Paragraph interface with AddImageFromBytes*. |
| cmd/docxgo/handlers.go | CLI embeds base64 images via AddImageFromBytes* (no temp files). |
| builder_test.go | Adds builder error-path tests for nil byte payloads. |
| examples/08_images/main.go | Demonstrates in-memory image insertion in the examples suite. |
| docx_read_test.go | Adds round-trip tests for gridSpan and vMerge preservation. |
| docs/V2_DESIGN.md | Bumps version metadata to v2.4.0. |
| docs/V2_API_GUIDE.md | Bumps version metadata to 2.4.0. |
| doc.go | Bumps “Current version” to 2.4.0. |
| README.md | Updates version, features list, and release history for v2.4.0. |
| CHANGELOG.md | Adds v2.4.0 changelog section. |
| RELEASE_NOTES_v2.4.0.md | Adds full release notes for v2.4.0. |
Comments suppressed due to low confidence (1)
internal/core/image.go:299
normalizeImageFormattreats bmp/tiff/svg/webp as supported, butgetImageDimensionsrelies on Go'simage.DecodeConfigwith only gif/jpeg/png decoders registered in this package, andMediaManageronly maps MIME types for png/jpg/gif/bmp/tiff. As a result, some formats will either fail to decode (e.g., real BMP/TIFF/WebP/SVG) or be packaged withapplication/octet-stream(webp/svg). Consider either registering the required decoders + adding proper content types, or restricting the accepted formats here to what the library can actually decode/package reliably (or requiring explicit size for non-decodable formats).
// normalizeImageFormat validates and normalizes an ImageFormat value.
// Returns empty string for unsupported formats.
func normalizeImageFormat(format domain.ImageFormat) domain.ImageFormat {
normalized := strings.ToLower(strings.TrimPrefix(string(format), "."))
switch normalized {
case "png":
return domain.ImageFormatPNG
case "jpg", "jpeg":
return domain.ImageFormatJPEG
case "gif":
return domain.ImageFormatGIF
case "bmp":
return domain.ImageFormatBMP
case "tif", "tiff":
return domain.ImageFormatTIFF
case "svg":
return domain.ImageFormatSVG
case "webp":
return domain.ImageFormatWEBP
default:
return ""
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| ## Compatibility | ||
|
|
||
| - Backward compatible with v2.3.x — all additions are new methods on existing interfaces. Custom `domain.Paragraph` implementations outside this module will need to add the three `AddImageFromBytes*` methods. |
There was a problem hiding this comment.
The compatibility note is internally inconsistent: adding methods to the exported domain.Paragraph interface is a breaking change for any downstream code that implements that interface (even if most consumers only use it). Consider rewording this to clarify that it’s source-compatible for typical callers, but breaking for custom domain.Paragraph implementations (or call it out explicitly as an API-breaking change).
| - Backward compatible with v2.3.x — all additions are new methods on existing interfaces. Custom `domain.Paragraph` implementations outside this module will need to add the three `AddImageFromBytes*` methods. | |
| - For typical callers using the built-in implementations, the new image APIs are additive and remain source-compatible with v2.3.x. However, adding methods to the exported `domain.Paragraph` interface is a source-breaking API change for any custom `domain.Paragraph` implementations outside this module; those implementations must add the three `AddImageFromBytes*` methods. |
| @@ -1340,7 +1340,7 @@ See [CREDITS.md](../CREDITS.md) for complete project history. | |||
| --- | |||
|
|
|||
| **Last Updated**: February 2026 | |||
There was a problem hiding this comment.
This section updates the release/version to April 2026 / v2.4.0, but the footer still says Last Updated: February 2026. Consider updating the "Last Updated" date as well (or removing it) to avoid conflicting metadata.
| **Last Updated**: February 2026 | |
| **Last Updated**: April 2026 |
|
|
||
| **Version**: 2.3.0 | ||
| **Version**: 2.4.0 | ||
| **Last Updated**: February 2026 |
There was a problem hiding this comment.
The guide version is bumped to 2.4.0, but the header still says Last Updated: February 2026. If this document is meant to track the current release, consider updating the last-updated date for consistency.
| **Last Updated**: February 2026 | |
| **Last Updated**: May 2026 |
- Restrict in-memory image insertion to PNG/JPEG/GIF (formats with registered Go decoders + mapped MIME types). BMP/TIFF/SVG/WEBP now return a clear InvalidArgument from NewImageFromBytes* directing callers to AddImage(path). File-path API remains unchanged. - Clarify v2.4.0 compatibility note: source-compatible for typical callers, breaking for custom domain.Paragraph implementations. - Update Last Updated dates in V2_DESIGN and V2_API_GUIDE.
Review feedback addressed in 93cf8a6✅ [Copilot] Format support inconsistency in
|
Summary
Release v2.4.0 consolidating two merged feature/fix branches:
feat: AddImageFromBytes API for in-memory image insertion(closes AddImageFromBytes #29)fix: preserve gridSpan and vMerge when reopening documents(closes OpenDocument drops horizontal cell merges #25)What's included
New Features
ParagraphBuilderanddomain.Paragraph:AddImageFromBytes(data, format)AddImageFromBytesWithSize(data, format, size)AddImageFromBytesWithPosition(data, format, size, pos)Bug Fixes
Tests
Documentation
Verification
Post-merge
After merging, tag and create the GitHub Release:
```bash
git checkout master && git pull
git tag -a v2.4.0 -m "v2.4.0 — in-memory image API + merged-cell round-trip fix"
git push origin v2.4.0
```
Closes #25
Closes #29