mirror of
https://github.com/storytold/wordcraft.git
synced 2026-11-14 01:48:21 -06:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66041f2a34 | ||
|
|
0db15a1d5e | ||
|
|
33afe69094 | ||
|
|
669d5df0b1 | ||
|
|
d1a36fd0c6 | ||
|
|
beae4b82fa | ||
|
|
514085ca44 |
Generated
+14
-14
@@ -1022,17 +1022,17 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "craft-cfb"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=75c70fa6a786240eb853e6267e25fc445f510cd0#75c70fa6a786240eb853e6267e25fc445f510cd0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=9dcbd90c7d16e1a668b3afa057e940e2db328b93#9dcbd90c7d16e1a668b3afa057e940e2db328b93"
|
||||
|
||||
[[package]]
|
||||
name = "craft-codepage"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=75c70fa6a786240eb853e6267e25fc445f510cd0#75c70fa6a786240eb853e6267e25fc445f510cd0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=9dcbd90c7d16e1a668b3afa057e940e2db328b93#9dcbd90c7d16e1a668b3afa057e940e2db328b93"
|
||||
|
||||
[[package]]
|
||||
name = "craft-doc-binary"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=75c70fa6a786240eb853e6267e25fc445f510cd0#75c70fa6a786240eb853e6267e25fc445f510cd0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=9dcbd90c7d16e1a668b3afa057e940e2db328b93#9dcbd90c7d16e1a668b3afa057e940e2db328b93"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"craft-cfb",
|
||||
@@ -1048,7 +1048,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "craft-docx"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=75c70fa6a786240eb853e6267e25fc445f510cd0#75c70fa6a786240eb853e6267e25fc445f510cd0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=9dcbd90c7d16e1a668b3afa057e940e2db328b93#9dcbd90c7d16e1a668b3afa057e940e2db328b93"
|
||||
dependencies = [
|
||||
"base64 0.23.1",
|
||||
"chrono",
|
||||
@@ -1068,7 +1068,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "craft-drawingml"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=75c70fa6a786240eb853e6267e25fc445f510cd0#75c70fa6a786240eb853e6267e25fc445f510cd0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=9dcbd90c7d16e1a668b3afa057e940e2db328b93#9dcbd90c7d16e1a668b3afa057e940e2db328b93"
|
||||
dependencies = [
|
||||
"craft-xml",
|
||||
"kurbo",
|
||||
@@ -1079,7 +1079,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "craft-flow"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=75c70fa6a786240eb853e6267e25fc445f510cd0#75c70fa6a786240eb853e6267e25fc445f510cd0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=9dcbd90c7d16e1a668b3afa057e940e2db328b93#9dcbd90c7d16e1a668b3afa057e940e2db328b93"
|
||||
dependencies = [
|
||||
"craft-wordmodel",
|
||||
"image",
|
||||
@@ -1148,7 +1148,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "craft-math"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=75c70fa6a786240eb853e6267e25fc445f510cd0#75c70fa6a786240eb853e6267e25fc445f510cd0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=9dcbd90c7d16e1a668b3afa057e940e2db328b93#9dcbd90c7d16e1a668b3afa057e940e2db328b93"
|
||||
dependencies = [
|
||||
"craft-xml",
|
||||
"serde",
|
||||
@@ -1157,7 +1157,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "craft-metafile"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=75c70fa6a786240eb853e6267e25fc445f510cd0#75c70fa6a786240eb853e6267e25fc445f510cd0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=9dcbd90c7d16e1a668b3afa057e940e2db328b93#9dcbd90c7d16e1a668b3afa057e940e2db328b93"
|
||||
dependencies = [
|
||||
"encoding_rs",
|
||||
"image",
|
||||
@@ -1168,7 +1168,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "craft-offcrypto"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=75c70fa6a786240eb853e6267e25fc445f510cd0#75c70fa6a786240eb853e6267e25fc445f510cd0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=9dcbd90c7d16e1a668b3afa057e940e2db328b93#9dcbd90c7d16e1a668b3afa057e940e2db328b93"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"base64 0.23.1",
|
||||
@@ -1185,7 +1185,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "craft-opc"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=75c70fa6a786240eb853e6267e25fc445f510cd0#75c70fa6a786240eb853e6267e25fc445f510cd0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=9dcbd90c7d16e1a668b3afa057e940e2db328b93#9dcbd90c7d16e1a668b3afa057e940e2db328b93"
|
||||
dependencies = [
|
||||
"craft-xml",
|
||||
"craft-zip",
|
||||
@@ -1230,7 +1230,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "craft-rtf"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=75c70fa6a786240eb853e6267e25fc445f510cd0#75c70fa6a786240eb853e6267e25fc445f510cd0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=9dcbd90c7d16e1a668b3afa057e940e2db328b93#9dcbd90c7d16e1a668b3afa057e940e2db328b93"
|
||||
dependencies = [
|
||||
"craft-codepage",
|
||||
"craft-flow",
|
||||
@@ -1329,7 +1329,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "craft-wordmodel"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=75c70fa6a786240eb853e6267e25fc445f510cd0#75c70fa6a786240eb853e6267e25fc445f510cd0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=9dcbd90c7d16e1a668b3afa057e940e2db328b93#9dcbd90c7d16e1a668b3afa057e940e2db328b93"
|
||||
dependencies = [
|
||||
"base64 0.23.1",
|
||||
"craft-math",
|
||||
@@ -1343,7 +1343,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "craft-xml"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=75c70fa6a786240eb853e6267e25fc445f510cd0#75c70fa6a786240eb853e6267e25fc445f510cd0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=9dcbd90c7d16e1a668b3afa057e940e2db328b93#9dcbd90c7d16e1a668b3afa057e940e2db328b93"
|
||||
dependencies = [
|
||||
"quick-xml 0.41.0",
|
||||
"thiserror 2.0.21",
|
||||
@@ -1352,7 +1352,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "craft-zip"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=75c70fa6a786240eb853e6267e25fc445f510cd0#75c70fa6a786240eb853e6267e25fc445f510cd0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=9dcbd90c7d16e1a668b3afa057e940e2db328b93#9dcbd90c7d16e1a668b3afa057e940e2db328b93"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"thiserror 2.0.21",
|
||||
|
||||
+4
-4
@@ -70,10 +70,10 @@ image = { version = "0.25", default-features = false, features = ["png", "jpeg",
|
||||
craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "6e03f65bf45181db77ba6229716e6624db9eb25b", version = "=0.1.0", default-features = false, features = ["write"] }
|
||||
# craft-update: app-specific pretend versions apply to startup and manual checks.
|
||||
craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "6e03f65bf45181db77ba6229716e6624db9eb25b", features = ["egui"] }
|
||||
craft-wordmodel = { git = "https://github.com/storytold/craft-libs", rev = "75c70fa6a786240eb853e6267e25fc445f510cd0", version = "=0.1.0" }
|
||||
craft-docx = { git = "https://github.com/storytold/craft-libs", rev = "75c70fa6a786240eb853e6267e25fc445f510cd0", version = "=0.1.0" }
|
||||
craft-rtf = { git = "https://github.com/storytold/craft-libs", rev = "75c70fa6a786240eb853e6267e25fc445f510cd0", version = "=0.1.0", features = ["doc"] }
|
||||
craft-doc-binary = { git = "https://github.com/storytold/craft-libs", rev = "75c70fa6a786240eb853e6267e25fc445f510cd0", version = "=0.1.0" }
|
||||
craft-wordmodel = { git = "https://github.com/storytold/craft-libs", rev = "9dcbd90c7d16e1a668b3afa057e940e2db328b93", version = "=0.1.0" }
|
||||
craft-docx = { git = "https://github.com/storytold/craft-libs", rev = "9dcbd90c7d16e1a668b3afa057e940e2db328b93", version = "=0.1.0" }
|
||||
craft-rtf = { git = "https://github.com/storytold/craft-libs", rev = "9dcbd90c7d16e1a668b3afa057e940e2db328b93", version = "=0.1.0", features = ["doc"] }
|
||||
craft-doc-binary = { git = "https://github.com/storytold/craft-libs", rev = "9dcbd90c7d16e1a668b3afa057e940e2db328b93", version = "=0.1.0" }
|
||||
quick-xml = "0.41"
|
||||
zip = { version = "3", default-features = false, features = ["deflate"] }
|
||||
# zlib streams (compressed metafile pictures in `.doc` drawings); already in the lock as a dependency of other crates.
|
||||
|
||||
@@ -372,7 +372,7 @@ fn run_with_strict_cannot_forge_an_error_line_either() {
|
||||
assert_eq!(r.status.code(), Some(1));
|
||||
let text = stderr(&r);
|
||||
assert_eq!(text.lines().count(), 1, "{text}");
|
||||
assert!(text.contains("\\u{1b}") || text.contains("\\u{1b}[2J"), "{text}");
|
||||
assert!(text.contains("\\nwordcraft-cli: strict: forged") && text.contains("\\u{202e}"), "{text}");
|
||||
assert!(!text.contains('\u{1b}') && !text.contains('\u{202e}'), "{text:?}");
|
||||
assert!(!out.exists());
|
||||
std::fs::remove_dir_all(d).unwrap();
|
||||
|
||||
@@ -20,8 +20,7 @@ pub(crate) const KIND: &str = "wordcraft-fit-v1";
|
||||
pub(crate) fn write(fit: &FitState) -> Result<Vec<u8>, DocxError> {
|
||||
let json = serde_json::to_string(fit).map_err(|e| DocxError::Xml(e.to_string()))?;
|
||||
let esc_json = quick_xml::escape::escape(&json);
|
||||
Ok(format!(r#"<?xml version="1.0" encoding="UTF-8"?><{ROOT} xmlns="{NS}" kind="{KIND}"><fitState>{}</fitState></{ROOT}>"#, esc_json)
|
||||
.into_bytes())
|
||||
Ok(format!(r#"<?xml version="1.0" encoding="UTF-8"?><{ROOT} xmlns="{NS}" kind="{KIND}"><fitState>{}</fitState></{ROOT}>"#, esc_json).into_bytes())
|
||||
}
|
||||
|
||||
/// Read the state from a custom XML part's bytes, if it is ours.
|
||||
|
||||
+25
-30
@@ -8,10 +8,9 @@ mod fit;
|
||||
mod model_bridge;
|
||||
|
||||
pub use craft_docx::{
|
||||
DEFAULT_SPIN_COUNT, DocxError, EmbeddedFont, Flavor, MAX_PASSWORD_CHARS, MAX_SOURCES,
|
||||
MAX_XML_FILE, PASSWORD_REQUIRED, PasswordCheck, WRONG_PASSWORD, XmlKind, check_password,
|
||||
check_protection_password, encrypt, encrypt_standard_for_tests,
|
||||
encrypt_with_spin_count, package_title, protection_hash, sniff_xml,
|
||||
DEFAULT_SPIN_COUNT, DocxError, EmbeddedFont, Flavor, MAX_PASSWORD_CHARS, MAX_SOURCES, MAX_XML_FILE, PASSWORD_REQUIRED, PasswordCheck,
|
||||
WRONG_PASSWORD, XmlKind, check_password, check_protection_password, encrypt, encrypt_standard_for_tests, encrypt_with_spin_count, package_title,
|
||||
protection_hash, sniff_xml,
|
||||
};
|
||||
|
||||
pub fn decrypt(bytes: &[u8], password: Option<&str>) -> Result<Vec<u8>, DocxError> {
|
||||
@@ -56,19 +55,13 @@ pub fn read_with_password(bytes: &[u8], password: Option<&str>) -> Result<wordcr
|
||||
pub fn read_with(bytes: &[u8], opts: &ReadOptions) -> Result<wordcraft_doc::Document, DocxError> {
|
||||
let bridge_importer = |shared_chunk: &craft_docx::AltChunk| -> Option<craft_wordmodel::Document> {
|
||||
let importer = opts.chunks?;
|
||||
let chunk = AltChunk {
|
||||
content_type: shared_chunk.content_type,
|
||||
part: shared_chunk.part,
|
||||
bytes: shared_chunk.bytes,
|
||||
};
|
||||
let chunk = AltChunk { content_type: shared_chunk.content_type, part: shared_chunk.part, bytes: shared_chunk.bytes };
|
||||
let doc = importer(&chunk)?;
|
||||
to_shared_model(&doc).ok()
|
||||
};
|
||||
|
||||
let shared_opts = craft_docx::ReadOptions {
|
||||
password: opts.password,
|
||||
chunks: opts.chunks.as_ref().map(|_| &bridge_importer as craft_docx::ChunkImporter),
|
||||
};
|
||||
let shared_opts =
|
||||
craft_docx::ReadOptions { password: opts.password, chunks: opts.chunks.as_ref().map(|_| &bridge_importer as craft_docx::ChunkImporter) };
|
||||
|
||||
let shared = craft_docx::read_with(bytes, &shared_opts)?;
|
||||
from_shared_model(shared)
|
||||
@@ -105,21 +98,22 @@ pub fn write_wordml2003(doc: &wordcraft_doc::Document) -> Result<Vec<u8>, DocxEr
|
||||
}
|
||||
|
||||
/// Draw a chart.
|
||||
pub fn chart_items(
|
||||
spec: &wordcraft_doc::chart::ChartSpec,
|
||||
theme: &[wordcraft_doc::Rgb],
|
||||
w: f32,
|
||||
h: f32,
|
||||
) -> Vec<wordcraft_doc::graphic::GraphicItem> {
|
||||
let shared_spec: craft_wordmodel::chart::ChartSpec = model_bridge::convert_ref(spec).expect("chart spec convert");
|
||||
let shared_theme: Vec<craft_wordmodel::props::Rgb> = model_bridge::convert_ref(theme).expect("theme convert");
|
||||
pub fn chart_items(spec: &wordcraft_doc::chart::ChartSpec, theme: &[wordcraft_doc::Rgb], w: f32, h: f32) -> Vec<wordcraft_doc::graphic::GraphicItem> {
|
||||
let (Ok(shared_spec), Ok(shared_theme)) = (
|
||||
model_bridge::convert_ref::<_, craft_wordmodel::chart::ChartSpec>(spec),
|
||||
model_bridge::convert_ref::<_, Vec<craft_wordmodel::props::Rgb>>(theme),
|
||||
) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let shared_items = craft_docx::chart_items(&shared_spec, &shared_theme, w, h);
|
||||
model_bridge::convert(shared_items).expect("chart items convert")
|
||||
model_bridge::convert(shared_items).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Write a chart part.
|
||||
pub fn chart_xml(spec: &wordcraft_doc::chart::ChartSpec) -> Vec<u8> {
|
||||
let shared_spec: craft_wordmodel::chart::ChartSpec = model_bridge::convert_ref(spec).expect("chart spec convert");
|
||||
let Ok(shared_spec) = model_bridge::convert_ref(spec) else {
|
||||
return Vec::new();
|
||||
};
|
||||
craft_docx::chart_xml(&shared_spec).into_bytes()
|
||||
}
|
||||
|
||||
@@ -130,19 +124,20 @@ pub fn smart_art_items(
|
||||
w: f32,
|
||||
h: f32,
|
||||
) -> Vec<wordcraft_doc::graphic::GraphicItem> {
|
||||
let shared_spec: craft_wordmodel::smart_art::SmartArtSpec = model_bridge::convert_ref(spec).expect("smart art spec convert");
|
||||
let shared_theme: Vec<craft_wordmodel::props::Rgb> = model_bridge::convert_ref(theme).expect("theme convert");
|
||||
let (Ok(shared_spec), Ok(shared_theme)) = (
|
||||
model_bridge::convert_ref::<_, craft_wordmodel::smart_art::SmartArtSpec>(spec),
|
||||
model_bridge::convert_ref::<_, Vec<craft_wordmodel::props::Rgb>>(theme),
|
||||
) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let shared_items = craft_docx::smart_art_items(&shared_spec, &shared_theme, w, h);
|
||||
model_bridge::convert(shared_items).expect("smart art items convert")
|
||||
model_bridge::convert(shared_items).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Read bibliography sources from a `b:Sources` file.
|
||||
pub fn read_sources(bytes: &[u8]) -> Result<Sources, DocxError> {
|
||||
let shared = craft_docx::read_sources(bytes)?;
|
||||
Ok(Sources {
|
||||
sources: model_bridge::convert(shared.sources).map_err(|e| DocxError::Xml(e.to_string()))?,
|
||||
style: shared.style,
|
||||
})
|
||||
Ok(Sources { sources: model_bridge::convert(shared.sources).map_err(|e| DocxError::Xml(e.to_string()))?, style: shared.style })
|
||||
}
|
||||
|
||||
/// The font files embedded in the document's font table.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Bounded conversion between craft-wordmodel and WordCraft's document model.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use craft_docx::DocxError;
|
||||
@@ -29,24 +30,29 @@ where
|
||||
// From Shared Model -> WordCraft Document
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn blocks_from_shared(source: craft_wordmodel::Blocks) -> Result<wordcraft_doc::Blocks, DocxError> {
|
||||
type GraphicCache = HashMap<usize, Arc<wordcraft_doc::graphic::Graphic>>;
|
||||
|
||||
fn blocks_from_shared(source: craft_wordmodel::Blocks, graphics: &mut GraphicCache) -> Result<wordcraft_doc::Blocks, DocxError> {
|
||||
source
|
||||
.into_iter()
|
||||
.map(|block| {
|
||||
let block = Arc::try_unwrap(block).unwrap_or_else(|shared| (*shared).clone());
|
||||
block_value_from_shared(block).map(Arc::new)
|
||||
block_value_from_shared(block, graphics).map(Arc::new)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn block_value_from_shared(source: craft_wordmodel::Block) -> Result<wordcraft_doc::Block, DocxError> {
|
||||
fn block_value_from_shared(source: craft_wordmodel::Block, graphics: &mut GraphicCache) -> Result<wordcraft_doc::Block, DocxError> {
|
||||
match source {
|
||||
craft_wordmodel::Block::Para(paragraph) => paragraph_value_from_shared(paragraph).map(wordcraft_doc::Block::Para),
|
||||
craft_wordmodel::Block::Table(table) => table_value_from_shared(table).map(wordcraft_doc::Block::Table),
|
||||
craft_wordmodel::Block::Para(paragraph) => paragraph_value_from_shared(paragraph, graphics).map(wordcraft_doc::Block::Para),
|
||||
craft_wordmodel::Block::Table(table) => table_value_from_shared(table, graphics).map(wordcraft_doc::Block::Table),
|
||||
}
|
||||
}
|
||||
|
||||
fn paragraph_value_from_shared(source: craft_wordmodel::para::Paragraph) -> Result<wordcraft_doc::para::Paragraph, DocxError> {
|
||||
fn paragraph_value_from_shared(
|
||||
source: craft_wordmodel::para::Paragraph,
|
||||
graphics: &mut GraphicCache,
|
||||
) -> Result<wordcraft_doc::para::Paragraph, DocxError> {
|
||||
let craft_wordmodel::para::Paragraph { text, runs, props, mark, objects, section, .. } = source;
|
||||
let mut paragraph = wordcraft_doc::para::Paragraph::new();
|
||||
paragraph.text = text;
|
||||
@@ -54,26 +60,20 @@ fn paragraph_value_from_shared(source: craft_wordmodel::para::Paragraph) -> Resu
|
||||
runs.into_iter().map(|run| Ok(wordcraft_doc::para::Run { len: run.len, props: convert(run.props)? })).collect::<Result<_, DocxError>>()?;
|
||||
paragraph.props = convert(props)?;
|
||||
paragraph.mark = convert(mark)?;
|
||||
paragraph.objects = objects.into_iter().map(object_value_from_shared).collect::<Result<_, _>>()?;
|
||||
paragraph.objects = objects.into_iter().map(|o| object_value_from_shared(o, graphics)).collect::<Result<_, _>>()?;
|
||||
paragraph.section = section.map(|section| convert(*section).map(Box::new)).transpose()?;
|
||||
Ok(paragraph)
|
||||
}
|
||||
|
||||
fn object_value_from_shared(source: craft_wordmodel::para::InlineObject) -> Result<wordcraft_doc::para::InlineObject, DocxError> {
|
||||
fn object_value_from_shared(
|
||||
source: craft_wordmodel::para::InlineObject,
|
||||
graphics: &mut GraphicCache,
|
||||
) -> Result<wordcraft_doc::para::InlineObject, DocxError> {
|
||||
use craft_wordmodel::para::InlineObject as Source;
|
||||
use wordcraft_doc::para::InlineObject as Target;
|
||||
|
||||
match source {
|
||||
Source::Image {
|
||||
media,
|
||||
w,
|
||||
h,
|
||||
alt,
|
||||
float,
|
||||
crop,
|
||||
fx,
|
||||
ole,
|
||||
} => Ok(Target::Image {
|
||||
Source::Image { media, w, h, alt, float, crop, fx, ole } => Ok(Target::Image {
|
||||
media,
|
||||
w,
|
||||
h,
|
||||
@@ -83,49 +83,70 @@ fn object_value_from_shared(source: craft_wordmodel::para::InlineObject) -> Resu
|
||||
fx: convert(fx)?,
|
||||
ole: ole.map(|o| convert(Arc::try_unwrap(o).unwrap_or_else(|s| (*s).clone())).map(Arc::new)).transpose()?,
|
||||
}),
|
||||
Source::Graphic { w, h, alt, float, graphic } => {
|
||||
let key = Arc::as_ptr(&graphic) as usize;
|
||||
let graphic = if let Some(cached) = graphics.get(&key) {
|
||||
Arc::clone(cached)
|
||||
} else {
|
||||
let converted: Arc<wordcraft_doc::graphic::Graphic> = Arc::new(convert(Arc::try_unwrap(graphic).unwrap_or_else(|s| (*s).clone()))?);
|
||||
graphics.insert(key, Arc::clone(&converted));
|
||||
converted
|
||||
};
|
||||
Ok(Target::Graphic { w, h, alt, float: convert(float)?, graphic })
|
||||
}
|
||||
Source::Group { w, h, float, ch_w, ch_h, children, canvas } => Ok(Target::Group {
|
||||
w,
|
||||
h,
|
||||
float: convert(float)?,
|
||||
ch_w,
|
||||
ch_h,
|
||||
children: children.into_iter().map(group_child_value_from_shared).collect::<Result<_, _>>()?,
|
||||
children: children.into_iter().map(|c| group_child_value_from_shared(c, graphics)).collect::<Result<_, _>>()?,
|
||||
canvas: canvas.map(convert).transpose()?,
|
||||
}),
|
||||
other => convert(other),
|
||||
}
|
||||
}
|
||||
|
||||
fn group_child_value_from_shared(source: craft_wordmodel::para::GroupChild) -> Result<wordcraft_doc::para::GroupChild, DocxError> {
|
||||
Ok(wordcraft_doc::para::GroupChild { x: source.x, y: source.y, obj: object_value_from_shared(source.obj)? })
|
||||
fn group_child_value_from_shared(
|
||||
source: craft_wordmodel::para::GroupChild,
|
||||
graphics: &mut GraphicCache,
|
||||
) -> Result<wordcraft_doc::para::GroupChild, DocxError> {
|
||||
Ok(wordcraft_doc::para::GroupChild { x: source.x, y: source.y, obj: object_value_from_shared(source.obj, graphics)? })
|
||||
}
|
||||
|
||||
fn table_value_from_shared(source: craft_wordmodel::table::Table) -> Result<wordcraft_doc::table::Table, DocxError> {
|
||||
fn table_value_from_shared(source: craft_wordmodel::table::Table, graphics: &mut GraphicCache) -> Result<wordcraft_doc::table::Table, DocxError> {
|
||||
Ok(wordcraft_doc::table::Table {
|
||||
props: convert(source.props)?,
|
||||
grid: source.grid,
|
||||
rows: source.rows.into_iter().map(row_value_from_shared).collect::<Result<_, _>>()?,
|
||||
rows: source.rows.into_iter().map(|r| row_value_from_shared(r, graphics)).collect::<Result<_, _>>()?,
|
||||
controls: convert(source.controls)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn row_value_from_shared(source: craft_wordmodel::table::Row) -> Result<wordcraft_doc::table::Row, DocxError> {
|
||||
fn row_value_from_shared(source: craft_wordmodel::table::Row, graphics: &mut GraphicCache) -> Result<wordcraft_doc::table::Row, DocxError> {
|
||||
Ok(wordcraft_doc::table::Row {
|
||||
props: convert(source.props)?,
|
||||
cells: source.cells.into_iter().map(cell_value_from_shared).collect::<Result<_, _>>()?,
|
||||
cells: source.cells.into_iter().map(|c| cell_value_from_shared(c, graphics)).collect::<Result<_, _>>()?,
|
||||
controls: convert(source.controls)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn cell_value_from_shared(source: craft_wordmodel::table::Cell) -> Result<wordcraft_doc::table::Cell, DocxError> {
|
||||
Ok(wordcraft_doc::table::Cell { props: convert(source.props)?, blocks: blocks_from_shared(source.blocks)?, controls: convert(source.controls)? })
|
||||
fn cell_value_from_shared(source: craft_wordmodel::table::Cell, graphics: &mut GraphicCache) -> Result<wordcraft_doc::table::Cell, DocxError> {
|
||||
Ok(wordcraft_doc::table::Cell {
|
||||
props: convert(source.props)?,
|
||||
blocks: blocks_from_shared(source.blocks, graphics)?,
|
||||
controls: convert(source.controls)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn part_value_from_shared(source: craft_wordmodel::Part) -> Result<wordcraft_doc::Part, DocxError> {
|
||||
Ok(wordcraft_doc::Part { kind: convert(source.kind)?, blocks: blocks_from_shared(source.blocks)?, body: convert(source.body)? })
|
||||
fn part_value_from_shared(source: craft_wordmodel::Part, graphics: &mut GraphicCache) -> Result<wordcraft_doc::Part, DocxError> {
|
||||
Ok(wordcraft_doc::Part { kind: convert(source.kind)?, blocks: blocks_from_shared(source.blocks, graphics)?, body: convert(source.body)? })
|
||||
}
|
||||
|
||||
fn glossary_value_from_shared(source: craft_wordmodel::BuildingBlock) -> Result<wordcraft_doc::BuildingBlock, DocxError> {
|
||||
fn glossary_value_from_shared(
|
||||
source: craft_wordmodel::BuildingBlock,
|
||||
graphics: &mut GraphicCache,
|
||||
) -> Result<wordcraft_doc::BuildingBlock, DocxError> {
|
||||
Ok(wordcraft_doc::BuildingBlock {
|
||||
name: source.name,
|
||||
gallery: source.gallery,
|
||||
@@ -135,7 +156,7 @@ fn glossary_value_from_shared(source: craft_wordmodel::BuildingBlock) -> Result<
|
||||
style: source.style,
|
||||
types: source.types,
|
||||
behaviors: source.behaviors,
|
||||
blocks: blocks_from_shared(source.blocks)?,
|
||||
blocks: blocks_from_shared(source.blocks, graphics)?,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -147,7 +168,7 @@ fn missing_pictures_from_shared(source: craft_wordmodel::MissingPictures) -> Res
|
||||
target.part_names = source.part_names;
|
||||
target.media_parts = source.media_parts;
|
||||
for item in source.list {
|
||||
target.push(convert(item)?);
|
||||
target.list.push(convert(item)?);
|
||||
}
|
||||
Ok(target)
|
||||
}
|
||||
@@ -156,13 +177,12 @@ fn missing_pictures_from_shared(source: craft_wordmodel::MissingPictures) -> Res
|
||||
pub fn from_shared_model(mut source: craft_wordmodel::Document) -> Result<wordcraft_doc::Document, DocxError> {
|
||||
let mut fit_state = None;
|
||||
let fit_key = source.passthrough.keys().find(|k| k.eq_ignore_ascii_case(crate::fit::PART)).cloned();
|
||||
if let Some(key) = fit_key {
|
||||
if let Some(xml_bytes) = source.passthrough.get(&key) {
|
||||
if let Some(state) = crate::fit::read_from_bytes(xml_bytes) {
|
||||
fit_state = Some(state);
|
||||
source.passthrough.remove(&key);
|
||||
}
|
||||
}
|
||||
if let Some(key) = fit_key
|
||||
&& let Some(xml_bytes) = source.passthrough.get(&key)
|
||||
&& let Some(state) = crate::fit::read_from_bytes(xml_bytes)
|
||||
{
|
||||
fit_state = Some(state);
|
||||
source.passthrough.remove(&key);
|
||||
}
|
||||
|
||||
let craft_wordmodel::Document {
|
||||
@@ -186,10 +206,11 @@ pub fn from_shared_model(mut source: craft_wordmodel::Document) -> Result<wordcr
|
||||
..
|
||||
} = source;
|
||||
|
||||
let mut graphics = GraphicCache::new();
|
||||
let mut document = wordcraft_doc::Document::new();
|
||||
document.body = blocks_from_shared(body)?;
|
||||
document.body = blocks_from_shared(body, &mut graphics)?;
|
||||
document.last_section = convert(last_section)?;
|
||||
document.parts = parts.into_iter().map(|(id, part)| Ok((id, part_value_from_shared(part)?))).collect::<Result<_, DocxError>>()?;
|
||||
document.parts = parts.into_iter().map(|(id, part)| Ok((id, part_value_from_shared(part, &mut graphics)?))).collect::<Result<_, DocxError>>()?;
|
||||
document.styles = Arc::new(convert((*styles).clone())?);
|
||||
document.numbering = Arc::new(convert((*numbering).clone())?);
|
||||
document.comments = convert(comments)?;
|
||||
@@ -202,7 +223,7 @@ pub fn from_shared_model(mut source: craft_wordmodel::Document) -> Result<wordcr
|
||||
document.endnote_separators = convert(endnote_separators)?;
|
||||
document.media = media;
|
||||
document.passthrough = passthrough;
|
||||
document.glossary = glossary.into_iter().map(glossary_value_from_shared).collect::<Result<_, _>>()?;
|
||||
document.glossary = glossary.into_iter().map(|g| glossary_value_from_shared(g, &mut graphics)).collect::<Result<_, _>>()?;
|
||||
document.missing_pictures = missing_pictures_from_shared(missing_pictures)?;
|
||||
document.fit_state = fit_state;
|
||||
Ok(document)
|
||||
@@ -235,17 +256,24 @@ fn sanitize_para_props(mut props: wordcraft_doc::ParaProps) -> wordcraft_doc::Pa
|
||||
wordcraft_doc::props::LineSpacing::AtLeast(v) => *v = finite(*v),
|
||||
}
|
||||
}
|
||||
if let Some(v) = &mut props.indent_left { if !v.is_finite() { *v = 0.0; } }
|
||||
if let Some(v) = &mut props.indent_right { if !v.is_finite() { *v = 0.0; } }
|
||||
if let Some(v) = &mut props.indent_first { if !v.is_finite() { *v = 0.0; } }
|
||||
if let Some(v) = &mut props.space_before { if !v.is_finite() { *v = 0.0; } }
|
||||
if let Some(v) = &mut props.space_after { if !v.is_finite() { *v = 0.0; } }
|
||||
for field in [&mut props.indent_left, &mut props.indent_right, &mut props.indent_first, &mut props.space_before, &mut props.space_after] {
|
||||
if let Some(v) = field
|
||||
&& !v.is_finite()
|
||||
{
|
||||
*v = 0.0;
|
||||
}
|
||||
}
|
||||
props
|
||||
}
|
||||
|
||||
fn sanitize_char_props(mut props: wordcraft_doc::CharProps) -> wordcraft_doc::CharProps {
|
||||
if let Some(v) = &mut props.size { if !v.is_finite() { *v = 0.0; } }
|
||||
if let Some(v) = &mut props.spacing { if !v.is_finite() { *v = 0.0; } }
|
||||
for field in [&mut props.size, &mut props.spacing] {
|
||||
if let Some(v) = field
|
||||
&& !v.is_finite()
|
||||
{
|
||||
*v = 0.0;
|
||||
}
|
||||
}
|
||||
props
|
||||
}
|
||||
|
||||
@@ -265,31 +293,17 @@ fn paragraph_value_to_shared(source: &wordcraft_doc::para::Paragraph) -> Result<
|
||||
}
|
||||
|
||||
fn object_value_to_shared(source: &wordcraft_doc::para::InlineObject) -> Result<craft_wordmodel::para::InlineObject, DocxError> {
|
||||
use wordcraft_doc::para::InlineObject as Source;
|
||||
use craft_wordmodel::para::InlineObject as Target;
|
||||
use wordcraft_doc::para::InlineObject as Source;
|
||||
|
||||
match source {
|
||||
Source::Image {
|
||||
media,
|
||||
w,
|
||||
h,
|
||||
alt,
|
||||
float,
|
||||
crop,
|
||||
fx,
|
||||
ole,
|
||||
} => Ok(Target::Image {
|
||||
Source::Image { media, w, h, alt, float, crop, fx, ole } => Ok(Target::Image {
|
||||
media: media.clone(),
|
||||
w: finite(*w),
|
||||
h: finite(*h),
|
||||
alt: alt.clone(),
|
||||
float: convert_ref(float)?,
|
||||
crop: [
|
||||
finite(crop[0]),
|
||||
finite(crop[1]),
|
||||
finite(crop[2]),
|
||||
finite(crop[3]),
|
||||
],
|
||||
crop: [finite(crop[0]), finite(crop[1]), finite(crop[2]), finite(crop[3])],
|
||||
fx: convert_ref(fx)?,
|
||||
ole: ole.as_ref().map(|o| convert_ref(o.as_ref()).map(Arc::new)).transpose()?,
|
||||
}),
|
||||
@@ -336,11 +350,7 @@ fn cell_value_to_shared(source: &wordcraft_doc::table::Cell) -> Result<craft_wor
|
||||
}
|
||||
|
||||
fn part_value_to_shared(source: &wordcraft_doc::Part) -> Result<craft_wordmodel::Part, DocxError> {
|
||||
Ok(craft_wordmodel::Part {
|
||||
kind: convert_ref(&source.kind)?,
|
||||
blocks: blocks_to_shared(&source.blocks)?,
|
||||
body: convert_ref(&source.body)?,
|
||||
})
|
||||
Ok(craft_wordmodel::Part { kind: convert_ref(&source.kind)?, blocks: blocks_to_shared(&source.blocks)?, body: convert_ref(&source.body)? })
|
||||
}
|
||||
|
||||
fn glossary_value_to_shared(source: &wordcraft_doc::BuildingBlock) -> Result<craft_wordmodel::BuildingBlock, DocxError> {
|
||||
@@ -365,7 +375,7 @@ fn missing_pictures_to_shared(source: &wordcraft_doc::MissingPictures) -> Result
|
||||
target.part_names = source.part_names.clone();
|
||||
target.media_parts = source.media_parts.clone();
|
||||
for item in &source.list {
|
||||
target.push(convert_ref(item)?);
|
||||
target.list.push(convert_ref(item)?);
|
||||
}
|
||||
Ok(target)
|
||||
}
|
||||
|
||||
@@ -4575,7 +4575,7 @@ fn shape_gradients_and_transparency_are_read_and_survive_saving() {
|
||||
rot_with_shape: false,
|
||||
flip: GradientFlip::Xy,
|
||||
}))),
|
||||
Some(grad(vec![stop(0.0, ph, 0.0), stop(100.0, Rgb(0x20, 0x40, 0x60), 0.0)], lin(90.0, false), true)),
|
||||
Some(grad(vec![stop(0.0, ph, 0.0), stop(100.0, Rgb(44, 92, 140), 0.0)], lin(90.0, false), true)),
|
||||
Some(grad(vec![stop(0.0, ph, 0.0), stop(100.0, black, 0.0)], centre, true)),
|
||||
None,
|
||||
];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Rich Text Format adapter using craft-rtf.
|
||||
|
||||
pub use craft_rtf::{dttm_to_iso, iso_to_dttm};
|
||||
pub use craft_rtf::doc::esc;
|
||||
pub use craft_rtf::{dttm_to_iso, iso_to_dttm};
|
||||
|
||||
/// Import RTF bytes into WordCraft's document model.
|
||||
pub fn import(bytes: &[u8]) -> Result<wordcraft_doc::Document, String> {
|
||||
@@ -11,7 +11,9 @@ pub fn import(bytes: &[u8]) -> Result<wordcraft_doc::Document, String> {
|
||||
|
||||
/// Export a WordCraft document to RTF.
|
||||
pub fn export(doc: &wordcraft_doc::Document) -> String {
|
||||
let shared = wordcraft_docx::to_shared_model(doc).expect("model conversion");
|
||||
let Ok(shared) = wordcraft_docx::to_shared_model(doc) else {
|
||||
return String::new();
|
||||
};
|
||||
craft_rtf::doc::export(&shared)
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ pub const TABLE: &[(&str, Class)] = &[
|
||||
/// The L0 foundation is a small chain: `raster` builds on `color` and
|
||||
/// `geom`, which the §3 diagram draws on one line. The GPU backend (`gpu`)
|
||||
/// reuses the CPU reference (`compose`) for LUTs and parity tests.
|
||||
pub const INTRA_LAYER_ORDER: &[&[&str]] = &[&["doc", "fonts"]];
|
||||
pub const INTRA_LAYER_ORDER: &[&[&str]] = &[&["doc", "fonts"], &["docx", "formats"]];
|
||||
|
||||
fn intra_layer_allowed(from: &str, to: &str) -> bool {
|
||||
let (from, to) = (short_name(from), short_name(to));
|
||||
|
||||
Reference in New Issue
Block a user