mod binary_writer;
mod r12;
mod section_writer;
mod stream_writer;
mod text_writer;
pub use binary_writer::DxfBinaryWriter;
pub use section_writer::SectionWriter;
pub use stream_writer::{value_type_for_code, DxfStreamWriter, DxfStreamWriterExt};
pub use text_writer::DxfTextWriter;
use crate::document::CadDocument;
use crate::entities::EntityType;
use crate::error::Result;
use std::fs::File;
use std::io::{BufWriter, Write};
use std::path::Path;
pub(crate) fn degrees(radians: f64) -> f64 {
let degrees = radians.to_degrees();
if !degrees.is_finite() || degrees.to_radians() == radians {
return degrees;
}
let (mut down, mut up) = (degrees, degrees);
for _ in 0..4 {
up = up.next_up();
if up.to_radians() == radians {
return up;
}
down = down.next_down();
if down.to_radians() == radians {
return down;
}
}
degrees
}
pub struct DxfWriter<'a> {
document: &'a CadDocument,
pub binary: bool,
version: Option<crate::types::DxfVersion>,
}
impl<'a> DxfWriter<'a> {
pub fn new(document: &'a CadDocument) -> Self {
Self {
document,
binary: false,
version: None,
}
}
pub fn new_binary(document: &'a CadDocument) -> Self {
Self {
document,
binary: true,
version: None,
}
}
pub fn with_version(mut self, version: crate::types::DxfVersion) -> Self {
self.version = Some(version);
self
}
pub fn set_binary(&mut self, binary: bool) {
self.binary = binary;
}
pub fn write_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
self.validate_architecture_records()?;
let file = File::create(path)?;
let writer = BufWriter::new(file);
self.write_to_writer(writer)
}
pub fn write_to_writer<W: Write>(&self, writer: W) -> Result<()> {
self.validate_architecture_records()?;
if self.version == Some(crate::types::DxfVersion::AC1009) {
return self.write_r12(writer);
}
for (handle, object) in &self.document.objects {
if let crate::objects::ObjectType::Unknown {
type_name,
raw_dxf_codes: None,
..
} = object
{
if crate::objects::associative_canonical_name(type_name) == "ASSOC2DCONSTRAINTGROUP"
{
return Err(crate::error::DxfError::NotImplemented(format!(
"Cannot export undecoded constraint group {type_name} at handle {handle} to DXF; save as the source DWG version to preserve its payload"
)));
}
}
}
let output_version = self.output_version();
let encoding = if output_version < crate::types::DxfVersion::AC1021 {
crate::io::dxf::code_page::encoding_from_code_page(&self.document.header.code_page)
} else {
None
};
if self.binary {
let mut stream_writer = DxfBinaryWriter::new(writer)?;
if let Some(encoding) = encoding {
stream_writer.set_encoding(encoding);
}
self.write_dxf(&mut stream_writer, output_version)?;
stream_writer.flush()?;
} else {
let mut stream_writer = DxfTextWriter::new(writer);
if let Some(encoding) = encoding {
stream_writer.set_encoding(encoding);
}
self.write_dxf(&mut stream_writer, output_version)?;
stream_writer.flush()?;
}
Ok(())
}
fn validate_architecture_records(&self) -> Result<()> {
let r12 = self.version == Some(crate::types::DxfVersion::AC1009);
let reason = if r12 {
"R12 does not support Arch classes"
} else {
"a native DXF class body is required"
};
for entity in self.document.entities() {
if let EntityType::Extended(entity) = entity {
if let crate::entities::ExtendedEntityData::Arch(data) = &entity.data {
if r12 || data.raw_dxf_codes.is_none() {
return Err(crate::error::DxfError::NotImplemented(format!(
"Cannot export Arch entity {} at handle {} to DXF: {reason}; save as the source DWG version to preserve its payload",
data.dxf_name, entity.common.handle,
)));
}
}
}
}
for object in self.document.objects.values() {
if let crate::objects::ObjectType::Arch(object) = object {
if r12 || object.raw_dxf_codes.is_none() {
return Err(crate::error::DxfError::NotImplemented(format!(
"Cannot export Arch object {} at handle {} to DXF: {reason}; save as the source DWG version to preserve its payload",
object.dxf_name, object.handle,
)));
}
}
}
Ok(())
}
pub fn write_to_vec(&self) -> Result<Vec<u8>> {
let entity_count = self.document.entities().count();
let estimated = (entity_count + 64) * 512;
let mut buffer = Vec::with_capacity(estimated);
self.write_to_writer(&mut buffer)?;
Ok(buffer)
}
fn write_dxf<W: DxfStreamWriter>(
&self,
writer: &mut W,
output_version: crate::types::DxfVersion,
) -> Result<()> {
let mut prepared = crate::io::loft_parameters::prepared(self.document);
crate::io::dwg::dwg_writer::prepare_database_references(&mut prepared);
crate::io::dwg::dwg_writer::prepare_table_keys(&mut prepared);
if prepared.has_null_table_entries() {
prepared.to_mut().assign_table_entry_handles();
}
crate::io::dwg::dwg_writer::prepare_active_viewport_elevation(&mut prepared);
self.write_prepared_dxf(writer, prepared.as_ref(), output_version)
}
fn write_prepared_dxf<W: DxfStreamWriter>(
&self,
writer: &mut W,
document: &CadDocument,
output_version: crate::types::DxfVersion,
) -> Result<()> {
let handle_start = compute_max_handle(document);
let extra_handles = count_extra_handles(
document,
SectionWriter::<W>::writable_version(output_version),
);
let handle_seed = handle_start + extra_handles + 1;
let mut section_writer = SectionWriter::new(writer, handle_start, handle_seed);
section_writer.set_version(output_version);
section_writer.build_valid_handles(document);
section_writer.write_header(document)?;
section_writer.write_classes(document)?;
section_writer.write_tables(document)?;
section_writer.write_blocks(document)?;
section_writer.write_entities(document)?;
section_writer.write_objects(document)?;
section_writer.write_acdsdata()?;
writer.write_string(0, "EOF")?;
Ok(())
}
fn output_version(&self) -> crate::types::DxfVersion {
let version = self.version.unwrap_or(self.document.version);
if self.binary && version < crate::types::DxfVersion::AC1021 {
crate::types::DxfVersion::AC1024
} else {
version
}
}
fn write_r12<W: Write>(&self, writer: W) -> Result<()> {
if self.binary {
return Err(crate::error::DxfError::UnsupportedVersion(
"binary DXF R12 (AC1009); write R12 as ASCII DXF".into(),
));
}
let mut stream_writer = DxfTextWriter::new(writer);
if let Some(encoding) =
crate::io::dxf::code_page::encoding_from_code_page(&self.document.header.code_page)
{
stream_writer.set_encoding(encoding);
}
r12::write_r12(&mut stream_writer, self.document)?;
stream_writer.flush()?;
Ok(())
}
pub fn document(&self) -> &CadDocument {
&self.document
}
}
fn count_extra_handles(document: &CadDocument, version: crate::types::DxfVersion) -> u64 {
use crate::objects::ObjectType;
let mut count = 0u64;
for entity in document.entities() {
match entity {
EntityType::Polyline(polyline) => {
count += polyline.vertices.len() as u64 + 1;
}
EntityType::Polyline2D(polyline) => {
if version >= crate::types::DxfVersion::AC1015
&& section_writer::polyline2d_downsaves_to_lwpolyline(polyline)
{
continue;
}
count += polyline.vertices.len() as u64 + 1;
}
EntityType::Polyline3D(polyline) => {
for vertex in &polyline.vertices {
if vertex.handle.is_null() {
count += 1;
}
}
count += 1;
}
EntityType::PolyfaceMesh(mesh) => {
for vertex in &mesh.vertices {
if vertex.common.handle.is_null() {
count += 1;
}
}
for face in &mesh.faces {
if face.common.handle.is_null() {
count += 1;
}
}
if mesh.seqend_handle.is_none() {
count += 1;
}
}
EntityType::PolygonMesh(mesh) => {
for vertex in &mesh.vertices {
if vertex.common.handle.is_null() {
count += 1;
}
}
count += 1;
}
EntityType::Insert(insert) => {
if insert.has_attributes() {
count += insert
.attributes
.iter()
.filter(|attribute| attribute.common.handle.is_null())
.count() as u64;
count += 1;
}
}
_ => {}
}
}
for obj in document.objects.values() {
if let ObjectType::SortEntitiesTable(table) = obj {
count += table.len() as u64;
}
}
count += document
.layers
.iter()
.filter(|layer| layer.handle.is_null())
.count() as u64;
count
}
fn compute_max_handle(document: &CadDocument) -> u64 {
let mut max = document.next_handle();
for entity in document.entities() {
let h = entity.common().handle.value();
if h >= max {
max = h + 1;
}
if let EntityType::Insert(insert) = entity {
for attribute in &insert.attributes {
max = max.max(attribute.common.handle.value().saturating_add(1));
}
}
}
for (handle, _) in &document.objects {
let h = handle.value();
if h >= max {
max = h + 1;
}
}
for br in document.block_records.iter() {
let h = br.handle.value();
if h >= max {
max = h + 1;
}
for eh in &br.entity_handles {
let h = eh.value();
if h >= max {
max = h + 1;
}
}
let h = br.block_entity_handle.value();
if h >= max {
max = h + 1;
}
let h = br.block_end_handle.value();
if h >= max {
max = h + 1;
}
}
for r in document.layers.iter() {
let h = r.handle.value();
if h >= max {
max = h + 1;
}
}
for r in document.line_types.iter() {
let h = r.handle.value();
if h >= max {
max = h + 1;
}
}
for r in document.text_styles.iter() {
let h = r.handle.value();
if h >= max {
max = h + 1;
}
}
for r in document.dim_styles.iter() {
let h = r.handle.value();
if h >= max {
max = h + 1;
}
}
for r in document.app_ids.iter() {
let h = r.handle.value();
if h >= max {
max = h + 1;
}
}
for r in document.views.iter() {
let h = r.handle.value();
if h >= max {
max = h + 1;
}
}
for r in document.vports.iter() {
let h = r.handle.value();
if h >= max {
max = h + 1;
}
}
for r in document.ucss.iter() {
let h = r.handle.value();
if h >= max {
max = h + 1;
}
}
max
}
pub fn write_dxf<P: AsRef<Path>>(document: &CadDocument, path: P) -> Result<()> {
let writer = DxfWriter::new(document);
writer.write_to_file(path)
}
pub fn write_binary_dxf<P: AsRef<Path>>(document: &CadDocument, path: P) -> Result<()> {
let writer = DxfWriter::new_binary(document);
writer.write_to_file(path)
}