mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Merge pull request #2688 from dotCooCoo/fix/pdf-import-drawn-space-gaps
Part of #2384: imported PDF text keeps its place when the file draws the space beside a wide gap
This commit is contained in:
@@ -154,15 +154,24 @@ fn area_type(nodes: &[Arc<Node>], lines: &HashMap<NodeId, LineFacts>, b: &Block)
|
||||
let texts: Vec<(&TextObject, &LineFacts)> = nodes.iter().map(|n| line(n, lines)).collect::<Option<_>>()?;
|
||||
let (first, _) = texts.first()?;
|
||||
let size = first.first_style().size;
|
||||
// Each line's tracking (a line with spaces tracked apart was set by hand).
|
||||
let trackings: Vec<f64> = texts
|
||||
.iter()
|
||||
.map(|(t, _)| {
|
||||
let mut all = t.runs.iter().map(|r| r.style.tracking);
|
||||
let one = all.next()?;
|
||||
all.all(|x| x == one).then_some(one)
|
||||
})
|
||||
.collect::<Option<_>>()?;
|
||||
// A line's tracking: one for all its runs, or (`spaces` false) one for its runs other than
|
||||
// lone spaces.
|
||||
let shared = |t: &TextObject, spaces: bool| {
|
||||
let mut all = t.runs.iter().filter(|r| spaces || r.text != " ").map(|r| r.style.tracking);
|
||||
let one = all.next()?;
|
||||
all.all(|x| x == one).then_some(one)
|
||||
};
|
||||
// As justified type, a wrapped line's tracking leaves out its spaces when each space is a run
|
||||
// of its own, tracked within a fiftieth of an em of the line's first space.
|
||||
let spread = |t: &TextObject| {
|
||||
let tracking = shared(t, false)?;
|
||||
let mut spaces = t.runs.iter().filter(|r| r.text == " ").map(|r| r.style.tracking);
|
||||
let alike =
|
||||
spaces.next().is_some_and(|s| spaces.all(|x| (x - s).abs() <= 20.0)) && !t.runs.iter().any(|r| r.text != " " && r.text.contains(' '));
|
||||
(alike || shared(t, true).is_some()).then_some(tracking)
|
||||
};
|
||||
let trackings: Option<Vec<f64>> = texts.iter().map(|(t, _)| shared(t, true)).collect();
|
||||
let justified: Option<Vec<f64>> = texts.iter().map(|(t, f)| if f.wraps { spread(t) } else { shared(t, true) }).collect();
|
||||
let median = |mut v: Vec<f64>| {
|
||||
v.sort_by(f64::total_cmp);
|
||||
v.get(v.len() / 2).copied()
|
||||
@@ -173,13 +182,18 @@ fn area_type(nodes: &[Arc<Node>], lines: &HashMap<NodeId, LineFacts>, b: &Block)
|
||||
// Wrapped lines all as long: justified (a paragraph's last line aligned left), tracked as the
|
||||
// lines that end paragraphs are (the others are spaced out).
|
||||
let full = longest(&wrapped);
|
||||
if wrapped.len() >= 2 && wrapped.iter().all(|l| (full - l).abs() <= size * 0.02) {
|
||||
let ends: Vec<f64> = texts.iter().zip(&trackings).filter(|((_, f), _)| !f.wraps).map(|(_, t)| *t).collect();
|
||||
let tracking = median(ends).or_else(|| trackings.iter().copied().reduce(f64::min))?;
|
||||
if wrapped.len() >= 2
|
||||
&& wrapped.iter().all(|l| (full - l).abs() <= size * 0.02)
|
||||
&& let Some(justified) = justified
|
||||
{
|
||||
let ends: Vec<f64> = texts.iter().zip(&justified).filter(|((_, f), _)| !f.wraps).map(|(_, t)| *t).collect();
|
||||
let tracking = median(ends).or_else(|| justified.iter().copied().reduce(f64::min))?;
|
||||
tries.push((Justify::JustifyLeft, full, tracking));
|
||||
}
|
||||
let all: Vec<f64> = texts.iter().map(|(_, f)| f.length).collect();
|
||||
tries.push((Justify::Left, longest(&all) + size * 0.02, median(trackings)?));
|
||||
if let Some(trackings) = trackings {
|
||||
tries.push((Justify::Left, longest(&all) + size * 0.02, median(trackings)?));
|
||||
}
|
||||
tries.into_iter().find_map(|(justify, width, tracking)| laid_out(&texts, b, justify, width, tracking))
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//! Glyphs each turned a little further along a curve (type set on a path: apps write each glyph
|
||||
//! with its own placement) make one type-on-a-path object, its path through the glyphs' baseline.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use kurbo::{Affine, BezPath, Point, Rect, Vec2};
|
||||
use vectorcraft_color::Paint;
|
||||
@@ -140,9 +140,13 @@ pub(crate) struct TextLine {
|
||||
/// Each glyph's ink box as drawn (document space), in drawing order; glyphs without ink are
|
||||
/// left out.
|
||||
pub ink: Vec<Rect>,
|
||||
/// The spaces read from gaps between glyphs: their byte offset in the text, and the gap from
|
||||
/// the end of the glyph before to the start of the next (points).
|
||||
/// The spaces read from gaps between glyphs, and the spaces the file draws between two glyphs
|
||||
/// that aren't spaces: their byte offset in the text, and the gap from the end of the glyph
|
||||
/// before to the start of the next (points).
|
||||
gaps: Vec<(usize, f64)>,
|
||||
/// A space the file drew after a glyph that isn't one, until the next glyph comes: its byte
|
||||
/// offset, and the gap before it plus its advance (points).
|
||||
drawn_space: Option<(usize, f64)>,
|
||||
/// Each glyph's advance in the file's font (points), with [`Self::baseline`].
|
||||
advances: Vec<f64>,
|
||||
/// The strokes of its glyphs' outlines drawn between them, one per glyph with ink so far
|
||||
@@ -180,6 +184,7 @@ impl TextLine {
|
||||
column: false,
|
||||
ink: vec![],
|
||||
gaps: vec![],
|
||||
drawn_space: None,
|
||||
advances: vec![],
|
||||
held: vec![],
|
||||
}
|
||||
@@ -268,6 +273,15 @@ impl TextLine {
|
||||
self.gaps.push((bytes, gap));
|
||||
}
|
||||
}
|
||||
// A space the file draws between two glyphs spans the gaps on both sides of it.
|
||||
let drawn = self.drawn_space.take();
|
||||
if on_line {
|
||||
if text == " " && !run.ends_with(' ') {
|
||||
self.drawn_space = Some((bytes, gap + advance));
|
||||
} else if let Some((b, before)) = drawn.filter(|_| !text.starts_with(' ')) {
|
||||
self.gaps.push((b, before + gap));
|
||||
}
|
||||
}
|
||||
if last.takes(look) {
|
||||
run.push_str(text);
|
||||
} else {
|
||||
@@ -403,7 +417,8 @@ impl TextLine {
|
||||
let size = self.at.size;
|
||||
let mut bounds = laid.bounds;
|
||||
if own.is_none() && chars > 1 && length.is_finite() && (length - natural).abs() > size * 0.01 {
|
||||
// The spaces read from gaps keep their own place only in text left in the file's order.
|
||||
// The spaces read from gaps or drawn between glyphs keep their own place only in text
|
||||
// left in the file's order.
|
||||
let total: usize = t.runs.iter().map(|r| r.text.len()).sum();
|
||||
let gaps: Vec<(usize, f64)> =
|
||||
if t.para.direction.is_none() { self.gaps.into_iter().filter(|(b, _)| *b < total).collect() } else { vec![] };
|
||||
@@ -472,24 +487,27 @@ fn tracked_each(runs: Vec<TextRun>, own: &[f64], size: f64) -> Vec<TextRun> {
|
||||
|
||||
/// The tracking (thousandths of an em) that sets a line of `chars` characters, laid out untracked
|
||||
/// as `laid`, `extra` points longer, as the file set it: the same after every character, except
|
||||
/// the spaces read from gaps (`gaps`: byte offset, gap in points) where that would put the glyphs
|
||||
/// after them more than a tenth of an em off (a barcode's digits, set in groups apart). Those take
|
||||
/// their own tracking, given with their byte offsets.
|
||||
/// the spaces read from gaps or drawn between glyphs (`gaps`: byte offset, gap in points) where
|
||||
/// that would put the glyphs after them more than a tenth of an em off (a barcode's digits, set in
|
||||
/// groups apart). Those take their own tracking, given with their byte offsets.
|
||||
fn spacing(laid: &TextLayout, gaps: &[(usize, f64)], extra: f64, chars: usize, size: f64) -> (f64, Vec<(usize, f64)>) {
|
||||
let pairs = chars.saturating_sub(1).max(1) as f64;
|
||||
// The advance of the first glyph laid out at each byte offset.
|
||||
let space: HashMap<usize, f64> = laid.glyphs.iter().rev().map(|g| (g.byte, g.advance)).collect();
|
||||
// The space and the character before it take `need` (the gap less the space) between them;
|
||||
// tracking gives them twice its share.
|
||||
let space = |b: usize| laid.glyphs.iter().find(|g| g.byte == b).map(|g| g.advance);
|
||||
let needs: Vec<(usize, f64)> = gaps.iter().filter_map(|&(b, gap)| Some((b, gap - space(b)?))).collect();
|
||||
let needs: Vec<(usize, f64)> = gaps.iter().filter_map(|&(b, gap)| Some((b, gap - space.get(&b)?))).collect();
|
||||
let mut own: Vec<(usize, f64)> = vec![];
|
||||
let mut taken: HashSet<usize> = HashSet::new();
|
||||
let mut tracking = extra / pairs;
|
||||
// Each space taken apart changes the others' share: until no more are.
|
||||
loop {
|
||||
let more: Vec<(usize, f64)> =
|
||||
needs.iter().filter(|(b, need)| !own.iter().any(|(o, _)| o == b) && (need - 2.0 * tracking).abs() > size * 0.1).copied().collect();
|
||||
needs.iter().filter(|(b, need)| !taken.contains(b) && (need - 2.0 * tracking).abs() > size * 0.1).copied().collect();
|
||||
if more.is_empty() {
|
||||
break;
|
||||
}
|
||||
taken.extend(more.iter().map(|(b, _)| *b));
|
||||
own.extend(more);
|
||||
let rest = pairs - 2.0 * own.len() as f64;
|
||||
tracking = if rest >= 1.0 { (extra - own.iter().map(|(_, need)| need).sum::<f64>()) / rest } else { 0.0 };
|
||||
|
||||
@@ -205,6 +205,27 @@ fn words_set_ems_apart_keep_their_place() {
|
||||
assert!((at(6) - f64::from(x2)).abs() < 0.05, "the third word starts where the file has it: {} vs {x2}", at(6));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn words_set_ems_apart_beside_the_spaces_the_file_draws_keep_their_place() {
|
||||
// The same words, the file drawing each space (2 pt wide) after a word, before the next one,
|
||||
// or alone with a gap on each side.
|
||||
let (x1, x2) = (48.0, 84.0);
|
||||
let drawings: [&[(f32, f32, f32, &str)]; 3] = [
|
||||
&[(20.0, 50.0, 10.0, "ab "), (x1, 50.0, 10.0, "cd "), (x2, 50.0, 10.0, "ef")],
|
||||
&[(20.0, 50.0, 10.0, "ab"), (x1 - 2.0, 50.0, 10.0, " cd"), (x2 - 2.0, 50.0, 10.0, " ef")],
|
||||
&[(20.0, 50.0, 10.0, "ab"), (36.0, 50.0, 10.0, " "), (x1, 50.0, 10.0, "cd"), (70.0, 50.0, 10.0, " "), (x2, 50.0, 10.0, "ef")],
|
||||
];
|
||||
for (k, lines) in drawings.iter().enumerate() {
|
||||
let t = texts(&import(&source_sans(lines)).unwrap());
|
||||
assert_eq!(t.len(), 1, "drawing {k}");
|
||||
assert_eq!(t[0].plain_text(), "ab cd ef", "drawing {k}");
|
||||
let laid = vectorcraft_text::layout(vectorcraft_text::FontDb::global(), &t[0]);
|
||||
let at = |byte: usize| (t[0].xf * laid.glyphs.iter().find(|g| g.byte == byte).unwrap().origin).x;
|
||||
assert!((at(3) - f64::from(x1)).abs() < 0.05, "drawing {k}: the second word starts where the file has it: {} vs {x1}", at(3));
|
||||
assert!((at(6) - f64::from(x2)).abs() < 0.05, "drawing {k}: the third word starts where the file has it: {} vs {x2}", at(6));
|
||||
}
|
||||
}
|
||||
|
||||
/// A document of area type `text` (Source Sans 3, 10 pt) `width` wide at (20, 30).
|
||||
fn area_doc(text: &str, justify: Justify, width: f64, space_before: f64) -> Document {
|
||||
let style = CharStyle { size: 10.0, fill: Paint::solid(Color::BLACK), ..Default::default() };
|
||||
@@ -247,6 +268,33 @@ fn lines_wrapped_in_a_frame_come_back_as_area_type() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn justified_lines_come_back_as_area_type_only_with_their_word_gaps_alike() {
|
||||
// "aa bb cc", "aa bb cc", "aa" with the file's spaces drawn, the wrapped lines 44 pt wide: "cc"
|
||||
// ends at 64, and "bb" starts where both gaps are 4.87 pt wider than a space, or elsewhere.
|
||||
let lines = |bb: f32| {
|
||||
[
|
||||
(20.0, 50.0, 10.0, "aa "),
|
||||
(bb, 50.0, 10.0, "bb "),
|
||||
(54.88, 50.0, 10.0, "cc "),
|
||||
(20.0, 62.0, 10.0, "aa "),
|
||||
(bb, 62.0, 10.0, "bb "),
|
||||
(54.88, 62.0, 10.0, "cc "),
|
||||
(20.0, 74.0, 10.0, "aa"),
|
||||
]
|
||||
};
|
||||
let t = texts(&import(&source_sans(&lines(36.95))).unwrap());
|
||||
assert_eq!(t.len(), 1, "one object: {:?}", t.iter().map(TextObject::plain_text).collect::<Vec<_>>());
|
||||
assert_eq!(t[0].plain_text(), "aa bb cc aa bb cc aa");
|
||||
assert_eq!(t[0].para.justify, Justify::JustifyLeft);
|
||||
// Gaps 1 and 8.74 pt wider than a space were set by hand: the lines keep their words in place.
|
||||
let t = texts(&import(&source_sans(&lines(33.08))).unwrap());
|
||||
assert_eq!(t.len(), 3, "{:?}", t.iter().map(TextObject::plain_text).collect::<Vec<_>>());
|
||||
let laid = vectorcraft_text::layout(vectorcraft_text::FontDb::global(), &t[0]);
|
||||
let bb = (t[0].xf * laid.glyphs.iter().find(|g| g.byte == 3).unwrap().origin).x;
|
||||
assert!((bb - 33.08).abs() < 0.05, "the second word starts where the file has it: {bb} vs 33.08");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lines_that_were_not_wrapped_stay_point_type() {
|
||||
// A list: each line its own, none ending in a space.
|
||||
|
||||
@@ -528,6 +528,33 @@ fn glyphs_spaced_each_their_own_way_keep_their_place() {
|
||||
});
|
||||
}
|
||||
|
||||
/// Words set apart by spaces tracked past an em (#2384) come back with each glyph in its place:
|
||||
/// the file draws each space and leaves the gap after it. Evenly tracked words keep one run.
|
||||
#[test]
|
||||
fn words_set_apart_by_spaces_tracked_past_an_em_keep_their_place() {
|
||||
let runs = [("Hooks", 0.0), (" ", 1800.0), ("and", 0.0), (" ", 2400.0), ("leads", 0.0)];
|
||||
let mut t = TextObject::point(Point::new(20.0, 100.0), "", style(12.0));
|
||||
t.runs = runs.iter().map(|(s, tr)| TextRun { text: s.to_string(), style: CharStyle { tracking: *tr, ..style(12.0) }, inline: None }).collect();
|
||||
let d = doc(vec![t]);
|
||||
let want = origins(&d);
|
||||
let back = import_as(&pdf(&d, false).bytes, TextAs::default());
|
||||
assert_eq!(texts(&back), ["Hooks and leads"]);
|
||||
let got = origins(&back);
|
||||
assert_eq!(got.len(), want.len());
|
||||
for (g, w) in got.iter().zip(&want) {
|
||||
assert!((g.x - w.x).abs() < 0.1 && (g.y - w.y).abs() < 0.1, "{got:?}\nvs {want:?}");
|
||||
}
|
||||
// Evenly tracked, the spaces too: one run.
|
||||
let even = doc(vec![TextObject::point(Point::new(20.0, 100.0), "Hooks and leads", CharStyle { tracking: 150.0, ..style(12.0) })]);
|
||||
let back = import_as(&pdf(&even, false).bytes, TextAs::default());
|
||||
assert_eq!(texts(&back), ["Hooks and leads"]);
|
||||
back.walk(|n| {
|
||||
if let NodeKind::Text(t) = &n.kind {
|
||||
assert_eq!(t.runs.len(), 1, "{:?}", t.runs.iter().map(|r| r.style.tracking).collect::<Vec<_>>());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Evenly tracked type on a path comes back with its tracking: each glyph where it was along the
|
||||
/// curve, not closed up to the font's advances.
|
||||
#[test]
|
||||
|
||||
@@ -754,6 +754,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
Exports refuse a `.ait` with an error that names `file.saveAsTemplate`, and `vectorcraft-cli convert in.pdf out.ait` and `run --export out.ait` write the template as `file.saveAsTemplate` does.
|
||||
- SVG/SVGZ in and out with SVG Options, Preserve Editing, symbols, filters, rich text and physical units (SVG type under a transform or `viewBox` scale opens at the size it draws at, the scale moved into its size, leading, baseline shift and strokes, a stretch, skew or rotation kept as its transform: #396; a top-level group with a clip path, such as a clipped Inkscape layer, opens as a layer with a clipping mask, its name kept: #420; hidden objects written hidden on Save and `convert`, and `data-*` attributes kept as the objects' own data: #522; top-level groups open as layers named from `data-name`, else the id, also when one more group (a transform on all the art, a `<switch>`) wraps them, hidden ones hidden, and a file whose art sits loose under a root `<svg>` with an id (or `data-name`), as one-layer exports write it, opens with its one layer named from it; nested groups stay groups, except the sublayers VectorCraft writes, marked `data-vc-layer`, and Inkscape sublayers, which open as sublayers in their layer's colour: #372); PDF and PDF-compatible `.ai` in and out (layers, with hidden ones kept hidden with their art and nested ones as sublayers, on pages of up to 64 million operators, art whose layer can't be told going to a hidden, non-printing layer rather than showing or printing: #419; strokes kept live, a fill and a stroke of one outline as one object; a type object's stroke written as each glyph's outline stroked back on the type, so it stays one editable object; lines wrapped in a frame (each wrapped line ending in a space, one left edge, leading and font) back as one area type object, left aligned or justified with its paragraph spacing, where laying it out breaks the lines where the file does; glyphs set apart by gaps wider than a space (a barcode's digit groups) keeping their places; layers stacked as the pages mark them even when one first paints on a later page, art ending a layer's marked content or a clip closing in a later layer kept in its own layer, and layers with no art kept empty: #508; art reaching past a plain PDF's page clipped to its crop box, as PDF viewers show it, each such object in a clip group of its own (a PDF-compatible `.ai` opens whole: #675); a `.ai` on its trim boxes with the bleed boxes round them as the document's bleed, as Crop To › Trim opens a PDF (#1266); masks, editable text with type set along a curve coming back as type on a path, each artboard's art on its own page only, an `.ai`'s white page under its layers left out rather than coming in as art, spot colours, presets, security, marks and bleed, ICC output intents, raster effects, subset fonts, PDF/X, PDF layers, overprint, page thumbnails, fast web view, PDF 1.3). CMYK images keep their ink amounts from PDF import (and from placed CMYK TIFFs) to PDF export: a CMYK JPEG passes through unchanged, others stay CMYK (DeviceCMYK, or ICC-based CMYK in PDF/X-3/X-4) unless the Output settings convert them; the canvas shows them through the working CMYK profile (#320). Overprint is kept from PDF import (opened, the PDF part of an `.ai`, placed) to PDF export: the graphics state's `/OP` and `/op` become the overprint of fills, strokes and type, so a spot colour over a process colour still overprints; a page whose overprint can't be read for certain knocks out with a note, as does `/OPM 0` being read as nonzero overprint (#1388; hand-written fixtures, `pdf/src/tests_import_overprint.rs`). A masked CMYK image (`/SMask`, `/Mask` or a colour key) keeps its inks too, its alpha coming in as a greyscale opacity mask.
|
||||
- SVG/SVGZ in and out with SVG Options, Preserve Editing, symbols, filters, rich text and physical units (SVG type under a transform or `viewBox` scale opens at the size it draws at, the scale moved into its size, leading, baseline shift and strokes, a stretch, skew or rotation kept as its transform: #396; a top-level group with a clip path, such as a clipped Inkscape layer, opens as a layer with a clipping mask, its name kept: #420; hidden objects written hidden on Save and `convert`, and `data-*` attributes kept as the objects' own data: #522; top-level groups open as layers named from `data-name`, else the id, also when one more group (a transform on all the art, a `<switch>`) wraps them, hidden ones hidden, and a file whose art sits loose under a root `<svg>` with an id (or `data-name`), as one-layer exports write it, opens with its one layer named from it; nested groups stay groups, except the sublayers VectorCraft writes, marked `data-vc-layer`, and Inkscape sublayers, which open as sublayers in their layer's colour: #372); PDF and PDF-compatible `.ai` in and out (layers, with hidden ones kept hidden with their art and nested ones as sublayers, on pages of up to 64 million operators, art whose layer can't be told going to a hidden, non-printing layer rather than showing or printing: #419; strokes kept live, a fill and a stroke of one outline as one object; a type object's stroke written as each glyph's outline stroked back on the type, so it stays one editable object; lines wrapped in a frame (each wrapped line ending in a space, one left edge, leading and font) back as one area type object, left aligned or justified with its paragraph spacing, where laying it out breaks the lines where the file does; glyphs set apart by gaps wider than a space (a barcode's digit groups) keeping their places; layers stacked as the pages mark them even when one first paints on a later page, art ending a layer's marked content or a clip closing in a later layer kept in its own layer, and layers with no art kept empty: #508; art reaching past a plain PDF's page clipped to its crop box, as PDF viewers show it, each such object in a clip group of its own (a PDF-compatible `.ai` opens whole: #675); a `.ai` on its trim boxes with the bleed boxes round them as the document's bleed, as Crop To › Trim opens a PDF (#1266); masks, editable text with type set along a curve coming back as type on a path, each artboard's art on its own page only, an `.ai`'s white page under its layers left out rather than coming in as art, spot colours, presets, security, marks and bleed, ICC output intents, raster effects, subset fonts, PDF/X, PDF layers, overprint, page thumbnails, fast web view, PDF 1.3). CMYK images keep their ink amounts from PDF import (and from placed CMYK TIFFs and the images in an `.ai` or EPS file's editing data, an alpha channel coming in as a grayscale opacity mask) to PDF export: a CMYK JPEG passes through unchanged, others stay CMYK (DeviceCMYK, or ICC-based CMYK in PDF/X-3/X-4) unless the Output settings convert them; the canvas shows them through the working CMYK profile (#320). Overprint is kept from PDF import (opened, the PDF part of an `.ai`, placed) to PDF export: the graphics state's `/OP` and `/op` become the overprint of fills, strokes and type, so a spot colour over a process colour still overprints; a page whose overprint can't be read for certain knocks out with a note, as does `/OPM 0` being read as nonzero overprint (#1388; hand-written fixtures, `pdf/src/tests_import_overprint.rs`). A masked CMYK image (`/SMask`, `/Mask` or a colour key) keeps its inks too, its alpha coming in as a greyscale opacity mask.
|
||||
In imported PDF type, a space glyph the file draws between two words also keeps the next word where the file has it when the gap beside the space is wide, as VectorCraft's own export draws a tracked space: the space gets tracking of its own, and the wrapped lines of a justified paragraph still come back as one area type object when the word gaps on each line are alike (part of #2384).
|
||||
SVG import's style cascade, used for hidden objects, non-scaling strokes and type, tests each element only against the `<style>` rules for its id, its classes and its element name, and the rules whose selector ends in none of these (`*`, `[x]`).
|
||||
A `.svgz` file copied in a file manager pastes as its art, as a copied `.svg` file does.
|
||||
In a `.ai` or EPS opened from its editing data, type has its text object's opacity and blend mode, whether it is made from the file's text document or taken from the page. Where such type has transparency and comes in with other art (the rest of its look, or page type and strokes that go with it), a group of them has it. Where the page draws its type, or the strokes around it, with transparency that is neither its text object's nor its text object's combined with that of the objects around it, the page's type and strokes keep their own instead (`crates/eps/src/import/tests_native.rs`). Where the page draws type of no text object over text objects made from the text document, that type is left out only when, for each piece of it, one of those text objects has the piece's Latin letters and digits in its text, in capitals or not; other such type is kept as the page's other type of no text object is.
|
||||
|
||||
Reference in New Issue
Block a user