forked from 1b-agent/opencadstudio-upstream
build: make the Trunk web-worker hook work on Windows
Trunk passes TRUNK_STAGING_DIR as an extended-length path (\?\C:\...) on Windows, which MSYS sh tools cannot stat, so post_build failed and the whole web build aborted. Strip the prefix with sed before use; Linux CI paths are unaffected. Also apply rustfmt to pdf_export.rs.
This commit is contained in:
1 parent
892d693d2d
commit
e3c86eca29
2 files changed
+410
-357
No files matched your search
@@ -2,7 +2,11 @@
|
|||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
cargo build --locked --release --target wasm32-unknown-unknown --package ocs_web_worker
|
cargo build --locked --release --target wasm32-unknown-unknown --package ocs_web_worker
|
||||||
worker_out="${TRUNK_STAGING_DIR:?}/worker_pkg"
|
# On Windows, Trunk passes an extended-length path (\\?\C:\...) that MSYS sh
|
||||||
|
# tools cannot handle; strip the prefix so local web builds work there too.
|
||||||
|
# CI (Linux) paths never carry the prefix and pass through unchanged.
|
||||||
|
staging=$(printf '%s\n' "${TRUNK_STAGING_DIR:?}" | sed 's|^\\\\?\\||')
|
||||||
|
worker_out="${staging:?}/worker_pkg"
|
||||||
mkdir -p "$worker_out"
|
mkdir -p "$worker_out"
|
||||||
wasm-bindgen \
|
wasm-bindgen \
|
||||||
--target web \
|
--target web \
|
||||||
|
|||||||
+405
-356
@@ -11,8 +11,8 @@
|
|||||||
use crate::io::plot_style::PlotStyleTable;
|
use crate::io::plot_style::PlotStyleTable;
|
||||||
use crate::scene::model::hatch_model::HatchModel;
|
use crate::scene::model::hatch_model::HatchModel;
|
||||||
use crate::scene::model::hatch_model::HatchPattern;
|
use crate::scene::model::hatch_model::HatchPattern;
|
||||||
use crate::scene::WireModel;
|
|
||||||
use crate::scene::model::image_model::ImageModel;
|
use crate::scene::model::image_model::ImageModel;
|
||||||
|
use crate::scene::WireModel;
|
||||||
use printpdf::{
|
use printpdf::{
|
||||||
BlendMode, BuiltinFont, Color, ExtendedGraphicsState, ExtendedGraphicsStateId, Line,
|
BlendMode, BuiltinFont, Color, ExtendedGraphicsState, ExtendedGraphicsStateId, Line,
|
||||||
LineCapStyle, LineDashPattern, LineJoinStyle, LinePoint, Mm, Op, PaintMode, PdfDocument,
|
LineCapStyle, LineDashPattern, LineJoinStyle, LinePoint, Mm, Op, PaintMode, PdfDocument,
|
||||||
@@ -280,16 +280,23 @@ fn emit_image(
|
|||||||
let mut height = image.height;
|
let mut height = image.height;
|
||||||
let mut pixels: std::borrow::Cow<[u8]> = std::borrow::Cow::Borrowed(image.pixels.as_slice());
|
let mut pixels: std::borrow::Cow<[u8]> = std::borrow::Cow::Borrowed(image.pixels.as_slice());
|
||||||
if let Some(target) = options.quality.target_dpi() {
|
if let Some(target) = options.quality.target_dpi() {
|
||||||
let dist = |a: [f32; 2], b: [f32; 2]| {
|
let dist =
|
||||||
((b[0] - a[0]).powi(2) + (b[1] - a[1]).powi(2)).sqrt()
|
|a: [f32; 2], b: [f32; 2]| ((b[0] - a[0]).powi(2) + (b[1] - a[1]).powi(2)).sqrt();
|
||||||
};
|
|
||||||
let width_edge = {
|
let width_edge = {
|
||||||
let d = dist(corners[0], corners[1]);
|
let d = dist(corners[0], corners[1]);
|
||||||
if d > 0.0 { d } else { dist(corners[0], corners[3]) }
|
if d > 0.0 {
|
||||||
|
d
|
||||||
|
} else {
|
||||||
|
dist(corners[0], corners[3])
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let height_edge = {
|
let height_edge = {
|
||||||
let d = dist(corners[0], corners[3]);
|
let d = dist(corners[0], corners[3]);
|
||||||
if d > 0.0 { d } else { dist(corners[0], corners[1]) }
|
if d > 0.0 {
|
||||||
|
d
|
||||||
|
} else {
|
||||||
|
dist(corners[0], corners[1])
|
||||||
|
}
|
||||||
};
|
};
|
||||||
// A degenerate (zero-area) placement keeps the native pixels.
|
// A degenerate (zero-area) placement keeps the native pixels.
|
||||||
if width_edge > 0.0 && height_edge > 0.0 {
|
if width_edge > 0.0 && height_edge > 0.0 {
|
||||||
@@ -299,18 +306,15 @@ fn emit_image(
|
|||||||
let (w, h) = downsample_target(width, height, dpi_x.max(dpi_y), target);
|
let (w, h) = downsample_target(width, height, dpi_x.max(dpi_y), target);
|
||||||
// Skip token reductions (< 5%): a Lanczos pass costs more than it saves.
|
// Skip token reductions (< 5%): a Lanczos pass costs more than it saves.
|
||||||
if (w != width || h != height) && w as f64 / (width as f64) < 0.95 {
|
if (w != width || h != height) && w as f64 / (width as f64) < 0.95 {
|
||||||
if let Some(resized) =
|
if let Some(resized) = image::RgbaImage::from_raw(
|
||||||
image::RgbaImage::from_raw(width, height, image.pixels.as_ref().clone())
|
width,
|
||||||
.map(|buffer| {
|
height,
|
||||||
image::imageops::resize(
|
image.pixels.as_ref().clone(),
|
||||||
&buffer,
|
)
|
||||||
w,
|
.map(|buffer| {
|
||||||
h,
|
image::imageops::resize(&buffer, w, h, image::imageops::FilterType::Lanczos3)
|
||||||
image::imageops::FilterType::Lanczos3,
|
.into_raw()
|
||||||
)
|
}) {
|
||||||
.into_raw()
|
|
||||||
})
|
|
||||||
{
|
|
||||||
width = w;
|
width = w;
|
||||||
height = h;
|
height = h;
|
||||||
pixels = std::borrow::Cow::Owned(resized);
|
pixels = std::borrow::Cow::Owned(resized);
|
||||||
@@ -468,8 +472,7 @@ pub fn pick_pdf_path_owned(
|
|||||||
.set_file_name(&format!("{stem}.pdf"))
|
.set_file_name(&format!("{stem}.pdf"))
|
||||||
.add_filter(crate::t!("PDF Files").as_ref(), &["pdf"])
|
.add_filter(crate::t!("PDF Files").as_ref(), &["pdf"])
|
||||||
.add_filter(crate::t!("All Files").as_ref(), &["*"])
|
.add_filter(crate::t!("All Files").as_ref(), &["*"])
|
||||||
.save_file()
|
.save_file()?;
|
||||||
?;
|
|
||||||
crate::config::remember_dialog_dir(&path);
|
crate::config::remember_dialog_dir(&path);
|
||||||
Some(path)
|
Some(path)
|
||||||
}
|
}
|
||||||
@@ -505,7 +508,13 @@ fn append_pdf_page(
|
|||||||
page: &PdfPageInput,
|
page: &PdfPageInput,
|
||||||
fallback_plot_style: Option<&PlotStyleTable>,
|
fallback_plot_style: Option<&PlotStyleTable>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let PlotContent { wires, hatches, wipeouts, images, group_splits } = &page.content;
|
let PlotContent {
|
||||||
|
wires,
|
||||||
|
hatches,
|
||||||
|
wipeouts,
|
||||||
|
images,
|
||||||
|
group_splits,
|
||||||
|
} = &page.content;
|
||||||
let (paper_w, paper_h) = (page.paper_w as f32, page.paper_h as f32);
|
let (paper_w, paper_h) = (page.paper_w as f32, page.paper_h as f32);
|
||||||
let (ox, oy) = (page.offset_x, page.offset_y);
|
let (ox, oy) = (page.offset_x, page.offset_y);
|
||||||
let (rotation_deg, scale, clip) = (page.rotation_deg, page.scale, page.clip);
|
let (rotation_deg, scale, clip) = (page.rotation_deg, page.scale, page.clip);
|
||||||
@@ -586,11 +595,17 @@ fn append_pdf_page(
|
|||||||
rings: vec![PolygonRing {
|
rings: vec![PolygonRing {
|
||||||
points: vec![
|
points: vec![
|
||||||
LinePoint {
|
LinePoint {
|
||||||
p: Point { x: Pt(cx * MM_TO_PT), y: Pt(cy * MM_TO_PT) },
|
p: Point {
|
||||||
|
x: Pt(cx * MM_TO_PT),
|
||||||
|
y: Pt(cy * MM_TO_PT),
|
||||||
|
},
|
||||||
bezier: false,
|
bezier: false,
|
||||||
},
|
},
|
||||||
LinePoint {
|
LinePoint {
|
||||||
p: Point { x: Pt((cx + cw) * MM_TO_PT), y: Pt(cy * MM_TO_PT) },
|
p: Point {
|
||||||
|
x: Pt((cx + cw) * MM_TO_PT),
|
||||||
|
y: Pt(cy * MM_TO_PT),
|
||||||
|
},
|
||||||
bezier: false,
|
bezier: false,
|
||||||
},
|
},
|
||||||
LinePoint {
|
LinePoint {
|
||||||
@@ -601,7 +616,10 @@ fn append_pdf_page(
|
|||||||
bezier: false,
|
bezier: false,
|
||||||
},
|
},
|
||||||
LinePoint {
|
LinePoint {
|
||||||
p: Point { x: Pt(cx * MM_TO_PT), y: Pt((cy + ch) * MM_TO_PT) },
|
p: Point {
|
||||||
|
x: Pt(cx * MM_TO_PT),
|
||||||
|
y: Pt((cy + ch) * MM_TO_PT),
|
||||||
|
},
|
||||||
bezier: false,
|
bezier: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -613,316 +631,326 @@ fn append_pdf_page(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let (first_wires, second_wires) = wires.split_at(group_splits.wires.min(wires.len()));
|
||||||
let (first_wires, second_wires) =
|
let (first_hatches, second_hatches) = hatches.split_at(group_splits.hatches.min(hatches.len()));
|
||||||
wires.split_at(group_splits.wires.min(wires.len()));
|
|
||||||
let (first_hatches, second_hatches) =
|
|
||||||
hatches.split_at(group_splits.hatches.min(hatches.len()));
|
|
||||||
let (first_wipeouts, second_wipeouts) =
|
let (first_wipeouts, second_wipeouts) =
|
||||||
wipeouts.split_at(group_splits.wipeouts.min(wipeouts.len()));
|
wipeouts.split_at(group_splits.wipeouts.min(wipeouts.len()));
|
||||||
let (first_images, second_images) =
|
let (first_images, second_images) = images.split_at(group_splits.images.min(images.len()));
|
||||||
images.split_at(group_splits.images.min(images.len()));
|
|
||||||
for (wires, hatches, wipeouts, images) in [
|
for (wires, hatches, wipeouts, images) in [
|
||||||
(first_wires, first_hatches, first_wipeouts, first_images),
|
(first_wires, first_hatches, first_wipeouts, first_images),
|
||||||
(second_wires, second_hatches, second_wipeouts, second_images),
|
(second_wires, second_hatches, second_wipeouts, second_images),
|
||||||
] {
|
] {
|
||||||
enum DrawItem<'a> {
|
enum DrawItem<'a> {
|
||||||
WireFill(&'a PlotWire),
|
WireFill(&'a PlotWire),
|
||||||
Hatch(&'a HatchModel),
|
Hatch(&'a HatchModel),
|
||||||
Image(&'a PlotImage),
|
Image(&'a PlotImage),
|
||||||
Wire(&'a PlotWire),
|
Wire(&'a PlotWire),
|
||||||
Text(&'a PlotWire),
|
Text(&'a PlotWire),
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut draw_items = Vec::with_capacity(wires.len() * 2 + hatches.len() + wipeouts.len() + images.len());
|
let mut draw_items =
|
||||||
let mut sequence = 0usize;
|
Vec::with_capacity(wires.len() * 2 + hatches.len() + wipeouts.len() + images.len());
|
||||||
for wire in wires {
|
let mut sequence = 0usize;
|
||||||
if !wire.fill_tris.is_empty() {
|
for wire in wires {
|
||||||
draw_items.push((wire.draw_depth, 0u8, sequence, DrawItem::WireFill(wire)));
|
if !wire.fill_tris.is_empty() {
|
||||||
|
draw_items.push((wire.draw_depth, 0u8, sequence, DrawItem::WireFill(wire)));
|
||||||
|
sequence += 1;
|
||||||
|
}
|
||||||
|
draw_items.push((wire.draw_depth, 2u8, sequence, DrawItem::Wire(wire)));
|
||||||
|
sequence += 1;
|
||||||
|
if !wire.text_verts.is_empty() {
|
||||||
|
draw_items.push((wire.draw_depth, 3u8, sequence, DrawItem::Text(wire)));
|
||||||
|
sequence += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for hatch in wipeouts.iter().chain(hatches.iter()) {
|
||||||
|
draw_items.push((hatch.draw_depth, 1u8, sequence, DrawItem::Hatch(hatch)));
|
||||||
sequence += 1;
|
sequence += 1;
|
||||||
}
|
}
|
||||||
draw_items.push((wire.draw_depth, 2u8, sequence, DrawItem::Wire(wire)));
|
for image in images {
|
||||||
sequence += 1;
|
draw_items.push((
|
||||||
if !wire.text_verts.is_empty() {
|
image.image.draw_depth,
|
||||||
draw_items.push((wire.draw_depth, 3u8, sequence, DrawItem::Text(wire)));
|
1u8,
|
||||||
|
sequence,
|
||||||
|
DrawItem::Image(image),
|
||||||
|
));
|
||||||
sequence += 1;
|
sequence += 1;
|
||||||
}
|
}
|
||||||
}
|
draw_items.sort_by(|a, b| {
|
||||||
for hatch in wipeouts.iter().chain(hatches.iter()) {
|
a.0.total_cmp(&b.0)
|
||||||
draw_items.push((hatch.draw_depth, 1u8, sequence, DrawItem::Hatch(hatch)));
|
.then_with(|| a.1.cmp(&b.1))
|
||||||
sequence += 1;
|
.then_with(|| a.2.cmp(&b.2))
|
||||||
}
|
});
|
||||||
for image in images {
|
|
||||||
draw_items.push((image.image.draw_depth, 1u8, sequence, DrawItem::Image(image)));
|
|
||||||
sequence += 1;
|
|
||||||
}
|
|
||||||
draw_items.sort_by(|a, b| {
|
|
||||||
a.0.total_cmp(&b.0)
|
|
||||||
.then_with(|| a.1.cmp(&b.1))
|
|
||||||
.then_with(|| a.2.cmp(&b.2))
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut last_color: Option<[f32; 3]> = None;
|
let mut last_color: Option<[f32; 3]> = None;
|
||||||
let mut last_lw: Option<f32> = None;
|
let mut last_lw: Option<f32> = None;
|
||||||
let mut last_cap = Some(LineCapStyle::Round);
|
let mut last_cap = Some(LineCapStyle::Round);
|
||||||
let mut last_join = Some(LineJoinStyle::Round);
|
let mut last_join = Some(LineJoinStyle::Round);
|
||||||
// Current PDF dash array (empty = solid). Tracked so the dash op is only
|
// Current PDF dash array (empty = solid). Tracked so the dash op is only
|
||||||
// re-emitted when it actually changes between wires.
|
// re-emitted when it actually changes between wires.
|
||||||
let mut last_dash: Option<Vec<i64>> = None;
|
let mut last_dash: Option<Vec<i64>> = None;
|
||||||
|
|
||||||
for (_, _, _, item) in draw_items {
|
for (_, _, _, item) in draw_items {
|
||||||
let wire = match item {
|
let wire = match item {
|
||||||
DrawItem::WireFill(wire) => {
|
DrawItem::WireFill(wire) => {
|
||||||
emit_wire_fills(
|
emit_wire_fills(
|
||||||
&mut ops,
|
&mut ops,
|
||||||
std::slice::from_ref(&wire.wire),
|
std::slice::from_ref(&wire.wire),
|
||||||
ox,
|
ox,
|
||||||
oy,
|
oy,
|
||||||
plot_style,
|
plot_style,
|
||||||
scale,
|
scale,
|
||||||
options,
|
options,
|
||||||
normal_blend.as_ref(),
|
normal_blend.as_ref(),
|
||||||
);
|
);
|
||||||
last_color = None;
|
last_color = None;
|
||||||
last_lw = None;
|
last_lw = None;
|
||||||
last_dash = None;
|
last_dash = None;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
DrawItem::Hatch(hatch) => {
|
|
||||||
emit_hatch(
|
|
||||||
&mut ops,
|
|
||||||
hatch,
|
|
||||||
ox,
|
|
||||||
oy,
|
|
||||||
plot_style,
|
|
||||||
scale,
|
|
||||||
options,
|
|
||||||
normal_blend.as_ref(),
|
|
||||||
);
|
|
||||||
last_color = None;
|
|
||||||
last_lw = None;
|
|
||||||
last_dash = None;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
DrawItem::Text(wire) => {
|
|
||||||
emit_text(
|
|
||||||
&mut ops,
|
|
||||||
std::slice::from_ref(&wire.wire),
|
|
||||||
ox,
|
|
||||||
oy,
|
|
||||||
scale,
|
|
||||||
plot_style,
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
last_color = None;
|
|
||||||
last_lw = None;
|
|
||||||
last_dash = None;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
DrawItem::Image(image) => {
|
|
||||||
emit_image(doc, image_resources, &mut ops, image, ox, oy, scale, options)?;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
DrawItem::Wire(wire) => wire,
|
|
||||||
};
|
|
||||||
let [mut r, mut g, mut b, a] = wire.color;
|
|
||||||
if a < 0.01 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Skip screen-only paper helpers. The PDF page supplies its own white
|
|
||||||
// boundary, and the printable-area rectangle is a UI guide, not ink.
|
|
||||||
if matches!(wire.name.as_str(), "__paper_boundary__" | "paper_printable_area") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Apply CTB plot style table overrides (color + lineweight).
|
|
||||||
let mut lw_override: Option<f32> = None;
|
|
||||||
let mut screening = 1.0;
|
|
||||||
let mut color_overridden = false;
|
|
||||||
let mut cap = None;
|
|
||||||
let mut join = None;
|
|
||||||
if let Some(ctb) = plot_style {
|
|
||||||
if wire.aci > 0 {
|
|
||||||
if let Some([cr, cg, cb]) = ctb.resolve_color(wire.aci) {
|
|
||||||
r = cr;
|
|
||||||
g = cg;
|
|
||||||
b = cb;
|
|
||||||
color_overridden = true;
|
|
||||||
}
|
|
||||||
lw_override = ctb
|
|
||||||
.resolve_lineweight(wire.aci)
|
|
||||||
.map(|mm| (mm * MM_TO_PT).max(0.1));
|
|
||||||
screening = ctb.resolve_screening(wire.aci);
|
|
||||||
if let Some(entry) = ctb.aci_entries.get(wire.aci as usize) {
|
|
||||||
cap = match entry.end_style {
|
|
||||||
0 => Some(LineCapStyle::Butt),
|
|
||||||
1 | 3 => Some(LineCapStyle::ProjectingSquare),
|
|
||||||
2 => Some(LineCapStyle::Round),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
join = match entry.join_style {
|
|
||||||
0 => Some(LineJoinStyle::Miter),
|
|
||||||
1 | 3 => Some(LineJoinStyle::Bevel),
|
|
||||||
2 => Some(LineJoinStyle::Round),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Near-white and near-yellow (viewport active border) → dark grey for print
|
|
||||||
// (only when no CTB override was applied).
|
|
||||||
if !color_overridden {
|
|
||||||
let is_light = r > 0.80 && g > 0.80 && b > 0.80;
|
|
||||||
let is_yellow = r > 0.80 && g > 0.70 && b < 0.30;
|
|
||||||
let is_cyan = r < 0.30 && g > 0.70 && b > 0.70;
|
|
||||||
if is_light || is_yellow {
|
|
||||||
r = 0.0;
|
|
||||||
g = 0.0;
|
|
||||||
b = 0.0;
|
|
||||||
} else if is_cyan {
|
|
||||||
// Viewport border: print as dark blue.
|
|
||||||
r = 0.0;
|
|
||||||
g = 0.15;
|
|
||||||
b = 0.50;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[r, g, b] = plotted_color([r, g, b], a, screening, options);
|
|
||||||
|
|
||||||
let cap = cap.unwrap_or(LineCapStyle::Round);
|
|
||||||
if last_cap != Some(cap) {
|
|
||||||
ops.push(Op::SetLineCapStyle { cap });
|
|
||||||
last_cap = Some(cap);
|
|
||||||
}
|
|
||||||
let join = join.unwrap_or(LineJoinStyle::Round);
|
|
||||||
if last_join != Some(join) {
|
|
||||||
ops.push(Op::SetLineJoinStyle { join });
|
|
||||||
last_join = Some(join);
|
|
||||||
}
|
|
||||||
|
|
||||||
if last_color
|
|
||||||
.map(|c| (c[0] - r).abs() > 0.01 || (c[1] - g).abs() > 0.01 || (c[2] - b).abs() > 0.01)
|
|
||||||
.unwrap_or(true)
|
|
||||||
{
|
|
||||||
let color = Color::Rgb(Rgb {
|
|
||||||
r,
|
|
||||||
g,
|
|
||||||
b,
|
|
||||||
icc_profile: None,
|
|
||||||
});
|
|
||||||
ops.push(Op::SetOutlineColor {
|
|
||||||
col: color.clone(),
|
|
||||||
});
|
|
||||||
ops.push(Op::SetFillColor { col: color });
|
|
||||||
last_color = Some([r, g, b]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Line weight: style override or object weight. Normal output divides
|
|
||||||
// by the page transform so physical pen widths stay constant; the
|
|
||||||
// scale-lineweights option deliberately keeps the transformed width.
|
|
||||||
//
|
|
||||||
// A wide polyline is the exception: its band is a geometric width in
|
|
||||||
// drawing units, so it must SCALE with the plot (no `/ scale`). Stroke
|
|
||||||
// the centre-line at `world_width`, converted mm → pt exactly like the
|
|
||||||
// geometry coordinates so the CTM scale renders the band at its true
|
|
||||||
// size; the linetype dash pattern below then strokes it dashed. This
|
|
||||||
// replaces the model-space hatch band that the shader-band change
|
|
||||||
// dropped, and overrides any CTB pen weight (the width is geometry, not
|
|
||||||
// a lineweight).
|
|
||||||
let pen_divisor = if options.scale_lineweights {
|
|
||||||
1.0
|
|
||||||
} else {
|
|
||||||
scale.max(1e-6)
|
|
||||||
};
|
|
||||||
let lw_pt = if wire.world_width > 0.0 {
|
|
||||||
wire.world_width * MM_TO_PT
|
|
||||||
} else {
|
|
||||||
let physical = if options.object_lineweights {
|
|
||||||
lw_override.unwrap_or_else(|| (wire.line_weight_px * LW_PX_TO_PT).max(0.1))
|
|
||||||
} else {
|
|
||||||
0.1
|
|
||||||
};
|
|
||||||
physical / pen_divisor
|
|
||||||
};
|
|
||||||
if last_lw.map(|l| (l - lw_pt).abs() > 0.01).unwrap_or(true) {
|
|
||||||
ops.push(Op::SetOutlineThickness { pt: Pt(lw_pt) });
|
|
||||||
last_lw = Some(lw_pt);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Linetype dash pattern. Without this every wire exported as a solid
|
|
||||||
// line regardless of its linetype (dashed / centre / dash-dot). (#155)
|
|
||||||
let dash_arr = dash_array_from_pattern(wire.pattern_length, &wire.pattern, MM_TO_PT);
|
|
||||||
let stationed =
|
|
||||||
!dash_arr.is_empty() && wire.pattern_stations.len() > wire.points.len();
|
|
||||||
if stationed {
|
|
||||||
if last_dash.as_ref().is_none_or(|dash| !dash.is_empty()) {
|
|
||||||
ops.push(Op::SetLineDashPattern {
|
|
||||||
dash: LineDashPattern::default(),
|
|
||||||
});
|
|
||||||
last_dash = Some(Vec::new());
|
|
||||||
}
|
|
||||||
for index in 0..wire.points.len().saturating_sub(1) {
|
|
||||||
if !wire.points[index][0].is_finite()
|
|
||||||
|| !wire.points[index + 1][0].is_finite()
|
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let start = wire.point_world(index, paper_h as f64 / scale.max(1e-6) as f64);
|
DrawItem::Hatch(hatch) => {
|
||||||
let end = wire.point_world(index + 1, paper_h as f64 / scale.max(1e-6) as f64);
|
emit_hatch(
|
||||||
for [from, to] in visible_station_ranges(
|
&mut ops,
|
||||||
wire.pattern_stations[index],
|
hatch,
|
||||||
wire.pattern_stations[index + 1],
|
ox,
|
||||||
wire.pattern_length,
|
oy,
|
||||||
&wire.pattern,
|
plot_style,
|
||||||
) {
|
scale,
|
||||||
let point = |t: f32| {
|
options,
|
||||||
LinePoint {
|
normal_blend.as_ref(),
|
||||||
|
);
|
||||||
|
last_color = None;
|
||||||
|
last_lw = None;
|
||||||
|
last_dash = None;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
DrawItem::Text(wire) => {
|
||||||
|
emit_text(
|
||||||
|
&mut ops,
|
||||||
|
std::slice::from_ref(&wire.wire),
|
||||||
|
ox,
|
||||||
|
oy,
|
||||||
|
scale,
|
||||||
|
plot_style,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
last_color = None;
|
||||||
|
last_lw = None;
|
||||||
|
last_dash = None;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
DrawItem::Image(image) => {
|
||||||
|
emit_image(
|
||||||
|
doc,
|
||||||
|
image_resources,
|
||||||
|
&mut ops,
|
||||||
|
image,
|
||||||
|
ox,
|
||||||
|
oy,
|
||||||
|
scale,
|
||||||
|
options,
|
||||||
|
)?;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
DrawItem::Wire(wire) => wire,
|
||||||
|
};
|
||||||
|
let [mut r, mut g, mut b, a] = wire.color;
|
||||||
|
if a < 0.01 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Skip screen-only paper helpers. The PDF page supplies its own white
|
||||||
|
// boundary, and the printable-area rectangle is a UI guide, not ink.
|
||||||
|
if matches!(
|
||||||
|
wire.name.as_str(),
|
||||||
|
"__paper_boundary__" | "paper_printable_area"
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Apply CTB plot style table overrides (color + lineweight).
|
||||||
|
let mut lw_override: Option<f32> = None;
|
||||||
|
let mut screening = 1.0;
|
||||||
|
let mut color_overridden = false;
|
||||||
|
let mut cap = None;
|
||||||
|
let mut join = None;
|
||||||
|
if let Some(ctb) = plot_style {
|
||||||
|
if wire.aci > 0 {
|
||||||
|
if let Some([cr, cg, cb]) = ctb.resolve_color(wire.aci) {
|
||||||
|
r = cr;
|
||||||
|
g = cg;
|
||||||
|
b = cb;
|
||||||
|
color_overridden = true;
|
||||||
|
}
|
||||||
|
lw_override = ctb
|
||||||
|
.resolve_lineweight(wire.aci)
|
||||||
|
.map(|mm| (mm * MM_TO_PT).max(0.1));
|
||||||
|
screening = ctb.resolve_screening(wire.aci);
|
||||||
|
if let Some(entry) = ctb.aci_entries.get(wire.aci as usize) {
|
||||||
|
cap = match entry.end_style {
|
||||||
|
0 => Some(LineCapStyle::Butt),
|
||||||
|
1 | 3 => Some(LineCapStyle::ProjectingSquare),
|
||||||
|
2 => Some(LineCapStyle::Round),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
join = match entry.join_style {
|
||||||
|
0 => Some(LineJoinStyle::Miter),
|
||||||
|
1 | 3 => Some(LineJoinStyle::Bevel),
|
||||||
|
2 => Some(LineJoinStyle::Round),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Near-white and near-yellow (viewport active border) → dark grey for print
|
||||||
|
// (only when no CTB override was applied).
|
||||||
|
if !color_overridden {
|
||||||
|
let is_light = r > 0.80 && g > 0.80 && b > 0.80;
|
||||||
|
let is_yellow = r > 0.80 && g > 0.70 && b < 0.30;
|
||||||
|
let is_cyan = r < 0.30 && g > 0.70 && b > 0.70;
|
||||||
|
if is_light || is_yellow {
|
||||||
|
r = 0.0;
|
||||||
|
g = 0.0;
|
||||||
|
b = 0.0;
|
||||||
|
} else if is_cyan {
|
||||||
|
// Viewport border: print as dark blue.
|
||||||
|
r = 0.0;
|
||||||
|
g = 0.15;
|
||||||
|
b = 0.50;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[r, g, b] = plotted_color([r, g, b], a, screening, options);
|
||||||
|
|
||||||
|
let cap = cap.unwrap_or(LineCapStyle::Round);
|
||||||
|
if last_cap != Some(cap) {
|
||||||
|
ops.push(Op::SetLineCapStyle { cap });
|
||||||
|
last_cap = Some(cap);
|
||||||
|
}
|
||||||
|
let join = join.unwrap_or(LineJoinStyle::Round);
|
||||||
|
if last_join != Some(join) {
|
||||||
|
ops.push(Op::SetLineJoinStyle { join });
|
||||||
|
last_join = Some(join);
|
||||||
|
}
|
||||||
|
|
||||||
|
if last_color
|
||||||
|
.map(|c| {
|
||||||
|
(c[0] - r).abs() > 0.01 || (c[1] - g).abs() > 0.01 || (c[2] - b).abs() > 0.01
|
||||||
|
})
|
||||||
|
.unwrap_or(true)
|
||||||
|
{
|
||||||
|
let color = Color::Rgb(Rgb {
|
||||||
|
r,
|
||||||
|
g,
|
||||||
|
b,
|
||||||
|
icc_profile: None,
|
||||||
|
});
|
||||||
|
ops.push(Op::SetOutlineColor { col: color.clone() });
|
||||||
|
ops.push(Op::SetFillColor { col: color });
|
||||||
|
last_color = Some([r, g, b]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Line weight: style override or object weight. Normal output divides
|
||||||
|
// by the page transform so physical pen widths stay constant; the
|
||||||
|
// scale-lineweights option deliberately keeps the transformed width.
|
||||||
|
//
|
||||||
|
// A wide polyline is the exception: its band is a geometric width in
|
||||||
|
// drawing units, so it must SCALE with the plot (no `/ scale`). Stroke
|
||||||
|
// the centre-line at `world_width`, converted mm → pt exactly like the
|
||||||
|
// geometry coordinates so the CTM scale renders the band at its true
|
||||||
|
// size; the linetype dash pattern below then strokes it dashed. This
|
||||||
|
// replaces the model-space hatch band that the shader-band change
|
||||||
|
// dropped, and overrides any CTB pen weight (the width is geometry, not
|
||||||
|
// a lineweight).
|
||||||
|
let pen_divisor = if options.scale_lineweights {
|
||||||
|
1.0
|
||||||
|
} else {
|
||||||
|
scale.max(1e-6)
|
||||||
|
};
|
||||||
|
let lw_pt = if wire.world_width > 0.0 {
|
||||||
|
wire.world_width * MM_TO_PT
|
||||||
|
} else {
|
||||||
|
let physical = if options.object_lineweights {
|
||||||
|
lw_override.unwrap_or_else(|| (wire.line_weight_px * LW_PX_TO_PT).max(0.1))
|
||||||
|
} else {
|
||||||
|
0.1
|
||||||
|
};
|
||||||
|
physical / pen_divisor
|
||||||
|
};
|
||||||
|
if last_lw.map(|l| (l - lw_pt).abs() > 0.01).unwrap_or(true) {
|
||||||
|
ops.push(Op::SetOutlineThickness { pt: Pt(lw_pt) });
|
||||||
|
last_lw = Some(lw_pt);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Linetype dash pattern. Without this every wire exported as a solid
|
||||||
|
// line regardless of its linetype (dashed / centre / dash-dot). (#155)
|
||||||
|
let dash_arr = dash_array_from_pattern(wire.pattern_length, &wire.pattern, MM_TO_PT);
|
||||||
|
let stationed = !dash_arr.is_empty() && wire.pattern_stations.len() > wire.points.len();
|
||||||
|
if stationed {
|
||||||
|
if last_dash.as_ref().is_none_or(|dash| !dash.is_empty()) {
|
||||||
|
ops.push(Op::SetLineDashPattern {
|
||||||
|
dash: LineDashPattern::default(),
|
||||||
|
});
|
||||||
|
last_dash = Some(Vec::new());
|
||||||
|
}
|
||||||
|
for index in 0..wire.points.len().saturating_sub(1) {
|
||||||
|
if !wire.points[index][0].is_finite() || !wire.points[index + 1][0].is_finite()
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let start = wire.point_world(index, paper_h as f64 / scale.max(1e-6) as f64);
|
||||||
|
let end = wire.point_world(index + 1, paper_h as f64 / scale.max(1e-6) as f64);
|
||||||
|
for [from, to] in visible_station_ranges(
|
||||||
|
wire.pattern_stations[index],
|
||||||
|
wire.pattern_stations[index + 1],
|
||||||
|
wire.pattern_length,
|
||||||
|
&wire.pattern,
|
||||||
|
) {
|
||||||
|
let point = |t: f32| LinePoint {
|
||||||
p: Point::new(
|
p: Point::new(
|
||||||
Mm((start.x + (end.x - start.x) * t as f64 + ox) as f32),
|
Mm((start.x + (end.x - start.x) * t as f64 + ox) as f32),
|
||||||
Mm((start.y + (end.y - start.y) * t as f64 + oy) as f32),
|
Mm((start.y + (end.y - start.y) * t as f64 + oy) as f32),
|
||||||
),
|
),
|
||||||
bezier: false,
|
bezier: false,
|
||||||
}
|
};
|
||||||
};
|
flush_line(&mut ops, &[point(from), point(to)], None);
|
||||||
flush_line(&mut ops, &[point(from), point(to)], None);
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if last_dash.as_deref() != Some(dash_arr.as_slice()) {
|
||||||
|
let dash = if dash_arr.is_empty() {
|
||||||
|
LineDashPattern::default()
|
||||||
|
} else {
|
||||||
|
LineDashPattern::from_array(&dash_arr, 0)
|
||||||
|
};
|
||||||
|
ops.push(Op::SetLineDashPattern { dash });
|
||||||
|
last_dash = Some(dash_arr.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit segments (NaN = pen-up). Points are the "high" half of a
|
||||||
|
// double-single pair; fold in the `points_low` residual and cancel the
|
||||||
|
// offset in f64 before narrowing. Dropping the residual (or narrowing
|
||||||
|
// first) snaps a UTM drawing onto the f32 grid — ~3 cm across, ~50 cm
|
||||||
|
// along northing — which is exactly the distortion the plot showed while
|
||||||
|
// low-coordinate drawings came out clean. The result is a sheet-mm value
|
||||||
|
// in single digits, so f32 is lossless from here.
|
||||||
|
let mut segment: Vec<LinePoint> = Vec::new();
|
||||||
|
let dot_radius = (wire.name == "viewport_hatch_pattern")
|
||||||
|
.then_some(Pt(SCREEN_DOT_MM * MM_TO_PT / (2.0 * scale.max(1e-6))));
|
||||||
|
for (pi, &[x, y, _z]) in wire.points.iter().enumerate() {
|
||||||
|
if x.is_nan() || y.is_nan() {
|
||||||
|
flush_line(&mut ops, &segment, dot_radius);
|
||||||
|
segment.clear();
|
||||||
|
} else {
|
||||||
|
let point = wire.point_world(pi, paper_h as f64 / scale.max(1e-6) as f64);
|
||||||
|
let wx = (point.x + ox) as f32;
|
||||||
|
let wy = (point.y + oy) as f32;
|
||||||
|
segment.push(LinePoint {
|
||||||
|
p: Point::new(Mm(wx), Mm(wy)),
|
||||||
|
bezier: false,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continue;
|
flush_line(&mut ops, &segment, dot_radius);
|
||||||
}
|
}
|
||||||
if last_dash.as_deref() != Some(dash_arr.as_slice()) {
|
|
||||||
let dash = if dash_arr.is_empty() {
|
|
||||||
LineDashPattern::default()
|
|
||||||
} else {
|
|
||||||
LineDashPattern::from_array(&dash_arr, 0)
|
|
||||||
};
|
|
||||||
ops.push(Op::SetLineDashPattern { dash });
|
|
||||||
last_dash = Some(dash_arr.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Emit segments (NaN = pen-up). Points are the "high" half of a
|
|
||||||
// double-single pair; fold in the `points_low` residual and cancel the
|
|
||||||
// offset in f64 before narrowing. Dropping the residual (or narrowing
|
|
||||||
// first) snaps a UTM drawing onto the f32 grid — ~3 cm across, ~50 cm
|
|
||||||
// along northing — which is exactly the distortion the plot showed while
|
|
||||||
// low-coordinate drawings came out clean. The result is a sheet-mm value
|
|
||||||
// in single digits, so f32 is lossless from here.
|
|
||||||
let mut segment: Vec<LinePoint> = Vec::new();
|
|
||||||
let dot_radius = (wire.name == "viewport_hatch_pattern")
|
|
||||||
.then_some(Pt(SCREEN_DOT_MM * MM_TO_PT / (2.0 * scale.max(1e-6))));
|
|
||||||
for (pi, &[x, y, _z]) in wire.points.iter().enumerate() {
|
|
||||||
if x.is_nan() || y.is_nan() {
|
|
||||||
flush_line(&mut ops, &segment, dot_radius);
|
|
||||||
segment.clear();
|
|
||||||
} else {
|
|
||||||
let point = wire.point_world(pi, paper_h as f64 / scale.max(1e-6) as f64);
|
|
||||||
let wx = (point.x + ox) as f32;
|
|
||||||
let wy = (point.y + oy) as f32;
|
|
||||||
segment.push(LinePoint {
|
|
||||||
p: Point::new(Mm(wx), Mm(wy)),
|
|
||||||
bezier: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
flush_line(&mut ops, &segment, dot_radius);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if needs_state {
|
if needs_state {
|
||||||
@@ -970,7 +998,10 @@ fn visible_station_ranges(
|
|||||||
pattern_length: f32,
|
pattern_length: f32,
|
||||||
pattern: &[f32; 8],
|
pattern: &[f32; 8],
|
||||||
) -> Vec<[f32; 2]> {
|
) -> Vec<[f32; 2]> {
|
||||||
let count = pattern.iter().rposition(|value| *value != 0.0).map_or(0, |i| i + 1);
|
let count = pattern
|
||||||
|
.iter()
|
||||||
|
.rposition(|value| *value != 0.0)
|
||||||
|
.map_or(0, |i| i + 1);
|
||||||
if count == 0 || pattern_length <= 1e-6 {
|
if count == 0 || pattern_length <= 1e-6 {
|
||||||
return vec![[0.0, 1.0]];
|
return vec![[0.0, 1.0]];
|
||||||
}
|
}
|
||||||
@@ -1016,7 +1047,11 @@ fn visible_station_ranges(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
if delta.abs() <= 1e-6 {
|
if delta.abs() <= 1e-6 {
|
||||||
return state(start, true).0.then_some([0.0, 1.0]).into_iter().collect();
|
return state(start, true)
|
||||||
|
.0
|
||||||
|
.then_some([0.0, 1.0])
|
||||||
|
.into_iter()
|
||||||
|
.collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut ranges = Vec::new();
|
let mut ranges = Vec::new();
|
||||||
@@ -1040,8 +1075,7 @@ fn flush_line(ops: &mut Vec<Op>, pts: &[LinePoint], dot_radius: Option<Pt>) {
|
|||||||
if let Some(radius) = dot_radius {
|
if let Some(radius) = dot_radius {
|
||||||
let first = pts[0].p;
|
let first = pts[0].p;
|
||||||
let coincident = pts.iter().skip(1).all(|point| {
|
let coincident = pts.iter().skip(1).all(|point| {
|
||||||
(point.p.x.0 - first.x.0).abs() <= 1e-6
|
(point.p.x.0 - first.x.0).abs() <= 1e-6 && (point.p.y.0 - first.y.0).abs() <= 1e-6
|
||||||
&& (point.p.y.0 - first.y.0).abs() <= 1e-6
|
|
||||||
});
|
});
|
||||||
if coincident {
|
if coincident {
|
||||||
emit_round_dot(ops, first, radius);
|
emit_round_dot(ops, first, radius);
|
||||||
@@ -1079,12 +1113,7 @@ fn emit_round_dot(ops: &mut Vec<Op>, center: Point, radius: Pt) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn plotted_color(
|
fn plotted_color(rgb: [f32; 3], alpha: f32, screening: f32, options: PdfPlotOptions) -> [f32; 3] {
|
||||||
rgb: [f32; 3],
|
|
||||||
alpha: f32,
|
|
||||||
screening: f32,
|
|
||||||
options: PdfPlotOptions,
|
|
||||||
) -> [f32; 3] {
|
|
||||||
let amount = screening.clamp(0.0, 1.0)
|
let amount = screening.clamp(0.0, 1.0)
|
||||||
* if options.transparency {
|
* if options.transparency {
|
||||||
alpha.clamp(0.0, 1.0)
|
alpha.clamp(0.0, 1.0)
|
||||||
@@ -1277,15 +1306,9 @@ fn emit_hatch(
|
|||||||
let mut styled_hatch = None;
|
let mut styled_hatch = None;
|
||||||
if let Some(table) = plot_style {
|
if let Some(table) = plot_style {
|
||||||
if hatch.aci > 0 && matches!(hatch.pattern, HatchPattern::Solid) {
|
if hatch.aci > 0 && matches!(hatch.pattern, HatchPattern::Solid) {
|
||||||
if let Some(pattern) = table
|
if let Some(pattern) = table.aci_entries.get(hatch.aci as usize).and_then(|entry| {
|
||||||
.aci_entries
|
crate::scene::model::hatch_model::plot_style_fill_pattern(entry.fill_style)
|
||||||
.get(hatch.aci as usize)
|
}) {
|
||||||
.and_then(|entry| {
|
|
||||||
crate::scene::model::hatch_model::plot_style_fill_pattern(
|
|
||||||
entry.fill_style,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
{
|
|
||||||
let mut model = hatch.clone();
|
let mut model = hatch.clone();
|
||||||
model.pattern = pattern;
|
model.pattern = pattern;
|
||||||
model.scale = 1.0 / scale.max(1.0e-6);
|
model.scale = 1.0 / scale.max(1.0e-6);
|
||||||
@@ -1329,8 +1352,7 @@ fn emit_hatch(
|
|||||||
r = 1.0;
|
r = 1.0;
|
||||||
g = 1.0;
|
g = 1.0;
|
||||||
b = 1.0;
|
b = 1.0;
|
||||||
} else if !color_overridden
|
} else if !color_overridden && !(hatch.aci == 7 && matches!(hatch.pattern, HatchPattern::Solid))
|
||||||
&& !(hatch.aci == 7 && matches!(hatch.pattern, HatchPattern::Solid))
|
|
||||||
{
|
{
|
||||||
let is_light = r > 0.80 && g > 0.80 && b > 0.80;
|
let is_light = r > 0.80 && g > 0.80 && b > 0.80;
|
||||||
let is_yellow = r > 0.80 && g > 0.70 && b < 0.30;
|
let is_yellow = r > 0.80 && g > 0.70 && b < 0.30;
|
||||||
@@ -1358,7 +1380,9 @@ fn emit_hatch(
|
|||||||
for &[bx, by] in hatch.boundary.iter() {
|
for &[bx, by] in hatch.boundary.iter() {
|
||||||
if bx.is_nan() || by.is_nan() {
|
if bx.is_nan() || by.is_nan() {
|
||||||
if current.len() >= 3 {
|
if current.len() >= 3 {
|
||||||
rings.push(PolygonRing { points: std::mem::take(&mut current) });
|
rings.push(PolygonRing {
|
||||||
|
points: std::mem::take(&mut current),
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
current.clear();
|
current.clear();
|
||||||
}
|
}
|
||||||
@@ -1432,9 +1456,7 @@ fn emit_hatch(
|
|||||||
b,
|
b,
|
||||||
icc_profile: None,
|
icc_profile: None,
|
||||||
});
|
});
|
||||||
ops.push(Op::SetOutlineColor {
|
ops.push(Op::SetOutlineColor { col: color.clone() });
|
||||||
col: color.clone(),
|
|
||||||
});
|
|
||||||
ops.push(Op::SetFillColor { col: color });
|
ops.push(Op::SetFillColor { col: color });
|
||||||
ops.push(Op::SetOutlineThickness {
|
ops.push(Op::SetOutlineThickness {
|
||||||
pt: Pt(physical / divisor),
|
pt: Pt(physical / divisor),
|
||||||
@@ -1573,16 +1595,19 @@ fn emit_text(
|
|||||||
if let Some(ctb) = plot_style {
|
if let Some(ctb) = plot_style {
|
||||||
if wire.aci > 0 {
|
if wire.aci > 0 {
|
||||||
ctb_color = ctb.resolve_color(wire.aci);
|
ctb_color = ctb.resolve_color(wire.aci);
|
||||||
lw_override = options.object_lineweights.then(|| {
|
lw_override = options
|
||||||
ctb.resolve_lineweight(wire.aci).map(|mm| {
|
.object_lineweights
|
||||||
let divisor = if options.scale_lineweights {
|
.then(|| {
|
||||||
1.0
|
ctb.resolve_lineweight(wire.aci).map(|mm| {
|
||||||
} else {
|
let divisor = if options.scale_lineweights {
|
||||||
scale.max(1e-6)
|
1.0
|
||||||
};
|
} else {
|
||||||
(mm * MM_TO_PT).max(0.1) / divisor
|
scale.max(1e-6)
|
||||||
|
};
|
||||||
|
(mm * MM_TO_PT).max(0.1) / divisor
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}).flatten();
|
.flatten();
|
||||||
screening = ctb.resolve_screening(wire.aci);
|
screening = ctb.resolve_screening(wire.aci);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1635,7 +1660,12 @@ fn emit_text(
|
|||||||
if !ge.fill_tris.is_empty() {
|
if !ge.fill_tris.is_empty() {
|
||||||
// Filled TrueType glyph: one filled triangle per triple.
|
// Filled TrueType glyph: one filled triangle per triple.
|
||||||
ops.push(Op::SetFillColor {
|
ops.push(Op::SetFillColor {
|
||||||
col: Color::Rgb(Rgb { r, g, b, icc_profile: None }),
|
col: Color::Rgb(Rgb {
|
||||||
|
r,
|
||||||
|
g,
|
||||||
|
b,
|
||||||
|
icc_profile: None,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
for tri in ge.fill_tris.chunks_exact(3) {
|
for tri in ge.fill_tris.chunks_exact(3) {
|
||||||
ops.push(Op::DrawPolygon {
|
ops.push(Op::DrawPolygon {
|
||||||
@@ -1643,7 +1673,10 @@ fn emit_text(
|
|||||||
rings: vec![PolygonRing {
|
rings: vec![PolygonRing {
|
||||||
points: tri
|
points: tri
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&p| LinePoint { p: map(p), bezier: false })
|
.map(|&p| LinePoint {
|
||||||
|
p: map(p),
|
||||||
|
bezier: false,
|
||||||
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
}],
|
}],
|
||||||
mode: PaintMode::Fill,
|
mode: PaintMode::Fill,
|
||||||
@@ -1688,7 +1721,10 @@ fn emit_text(
|
|||||||
line: Line {
|
line: Line {
|
||||||
points: stroke
|
points: stroke
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&p| LinePoint { p: map(p), bezier: false })
|
.map(|&p| LinePoint {
|
||||||
|
p: map(p),
|
||||||
|
bezier: false,
|
||||||
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
is_closed: false,
|
is_closed: false,
|
||||||
},
|
},
|
||||||
@@ -1699,14 +1735,22 @@ fn emit_text(
|
|||||||
// Decoration bar (underline / overline / strike): the quad is a
|
// Decoration bar (underline / overline / strike): the quad is a
|
||||||
// solid-texel rectangle — fill it directly from its corners.
|
// solid-texel rectangle — fill it directly from its corners.
|
||||||
ops.push(Op::SetFillColor {
|
ops.push(Op::SetFillColor {
|
||||||
col: Color::Rgb(Rgb { r, g, b, icc_profile: None }),
|
col: Color::Rgb(Rgb {
|
||||||
|
r,
|
||||||
|
g,
|
||||||
|
b,
|
||||||
|
icc_profile: None,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
ops.push(Op::DrawPolygon {
|
ops.push(Op::DrawPolygon {
|
||||||
polygon: Polygon {
|
polygon: Polygon {
|
||||||
rings: vec![PolygonRing {
|
rings: vec![PolygonRing {
|
||||||
points: [bl, br, tr, tl]
|
points: [bl, br, tr, tl]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&c| LinePoint { p: point(c[0], c[1]), bezier: false })
|
.map(|&c| LinePoint {
|
||||||
|
p: point(c[0], c[1]),
|
||||||
|
bezier: false,
|
||||||
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
}],
|
}],
|
||||||
mode: PaintMode::Fill,
|
mode: PaintMode::Fill,
|
||||||
@@ -1724,7 +1768,10 @@ mod tests {
|
|||||||
|
|
||||||
fn test_page(wires: Vec<PlotWire>) -> PdfPageInput {
|
fn test_page(wires: Vec<PlotWire>) -> PdfPageInput {
|
||||||
PdfPageInput {
|
PdfPageInput {
|
||||||
content: PlotContent { wires: std::sync::Arc::new(wires), ..Default::default() },
|
content: PlotContent {
|
||||||
|
wires: std::sync::Arc::new(wires),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
paper_w: 210.0,
|
paper_w: 210.0,
|
||||||
paper_h: 297.0,
|
paper_h: 297.0,
|
||||||
offset_x: 0.0,
|
offset_x: 0.0,
|
||||||
@@ -1838,11 +1885,13 @@ mod tests {
|
|||||||
(corners[3], [0.0, 0.0]),
|
(corners[3], [0.0, 0.0]),
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(pos, uv)| crate::scene::model::image_model::ImageQuadVertex {
|
.map(
|
||||||
pos: *pos,
|
|(pos, uv)| crate::scene::model::image_model::ImageQuadVertex {
|
||||||
uv: *uv,
|
pos: *pos,
|
||||||
pos_low: [0.0; 3],
|
uv: *uv,
|
||||||
})
|
pos_low: [0.0; 3],
|
||||||
|
},
|
||||||
|
)
|
||||||
.collect();
|
.collect();
|
||||||
PlotImage {
|
PlotImage {
|
||||||
image: ImageModel {
|
image: ImageModel {
|
||||||
|
|||||||
Reference in new issue
Block a user