Merge PR #4058: M12: type or pick an architect's 1:N drawing scale in the Measure panel, applied at once

Conflicts:
- crates/ui-egui/src/i18n/mod.rs: kept main's side (main dropped the per-language translation tests; the PR's drawing_scale_labels_are_translated_in_every_catalog is not carried over).
- parity/acrobat-features.toml: measure.distance evidence: kept main's paths (tests/it/), appended the PR's two new entries.
This commit is contained in:
aiongg
2026-10-24 06:23:49 +00:00
36 changed files with 403 additions and 3 deletions
+4
View File
@@ -2040,6 +2040,10 @@
Perimeter المحيط
Area المساحة
Drawing scale مقياس الرسم
Scale المقياس
Common scales مقاييس شائعة
Enter a scale such as 1:50. أدخل مقياسًا مثل 1:50.
Applying a scale switches units to m / mm. يؤدي تطبيق المقياس إلى تحويل الوحدات إلى م / مم.
Snapping الانجذاب
Export measurements تصدير القياسات
Open a PDF to measure it. افتح ملف PDF لقياسه.
Can't render this file because it contains an unexpected character in line 6 and column 47.
+4
View File
@@ -1902,6 +1902,10 @@
Perimeter Периметър
Area Площ
Drawing scale Мащаб на чертежа
Scale Мащаб
Common scales Често използвани мащаби
Enter a scale such as 1:50. Въведете мащаб, например 1:50.
Applying a scale switches units to m / mm. Прилагането на мащаб превключва единиците на m / mm.
Snapping Прилепване
Export measurements Експортирай измерванията
Open a PDF to measure it. Отворете PDF файл, за да го измервате.
Can't render this file because it contains an unexpected character in line 8 and column 16.
+4
View File
@@ -2000,6 +2000,10 @@ action wizard Start Inicia
Perimeter Perímetre
Area Àrea
Drawing scale Escala del dibuix
Scale Escala
Common scales Escales habituals
Enter a scale such as 1:50. Introduïu una escala com 1:50.
Applying a scale switches units to m / mm. En aplicar una escala, les unitats canvien a m / mm.
Snapping Ajust
Export measurements Exporta les mesures
Open a PDF to measure it. Obre un PDF per mesurar-lo.
Can't render this file because it contains an unexpected character in line 7 and column 3.
+4
View File
@@ -2003,6 +2003,10 @@
Perimeter چێوە
Area ڕووبەر
Drawing scale پێوەری وێنەکێشان
Scale پێوەر
Common scales پێوەرە باوەکان
Enter a scale such as 1:50. پێوەرێک بنووسە وەک 1:50.
Applying a scale switches units to m / mm. جێبەجێکردنی پێوەر یەکەکان دەگۆڕێت بۆ م / ملم.
Snapping لکاندن
Export measurements هەناردەکردنی پێوانەکان
Open a PDF to measure it. PDFێک بکەرەوە بۆ پێوانەکردنی.
Can't render this file because it contains an unexpected character in line 6 and column 47.
+4
View File
@@ -2090,6 +2090,10 @@ key Space Mezerník
Perimeter Obvod
Area Plocha
Drawing scale Měřítko výkresu
Scale Měřítko
Common scales Běžná měřítka
Enter a scale such as 1:50. Zadejte měřítko, například 1:50.
Applying a scale switches units to m / mm. Použití měřítka přepne jednotky na m / mm.
Snapping Přichytávání
Export measurements Exportovat měření
Open a PDF to measure it. Otevřete PDF, které chcete měřit.
Can't render this file because it contains an unexpected character in line 6 and column 47.
+4
View File
@@ -1976,6 +1976,10 @@
Perimeter Umfang
Area Fläche
Drawing scale Zeichnungsmaßstab
Scale Maßstab
Common scales Gängige Maßstäbe
Enter a scale such as 1:50. Geben Sie einen Maßstab wie 1:50 ein.
Applying a scale switches units to m / mm. Beim Anwenden eines Maßstabs werden die Einheiten auf m / mm umgestellt.
Snapping Einrasten
Export measurements Messungen exportieren
Open a PDF to measure it. Öffne ein PDF, um es zu messen.
Can't render this file because it contains an unexpected character in line 6 and column 51.
+4
View File
@@ -1417,6 +1417,10 @@
Perimeter Περίμετρος
Area Εμβαδόν
Drawing scale Κλίμακα σχεδίου
Scale Κλίμακα
Common scales Συνηθισμένες κλίμακες
Enter a scale such as 1:50. Εισαγάγετε μια κλίμακα, όπως 1:50.
Applying a scale switches units to m / mm. Η εφαρμογή κλίμακας αλλάζει τις μονάδες σε m / mm.
Snapping Προσκόλληση
Export measurements Εξαγωγή μετρήσεων
Open a PDF to measure it. Ανοίξτε ένα PDF για να πραγματοποιήσετε μετρήσεις.
Can't render this file because it contains an unexpected character in line 482 and column 9.
+4
View File
@@ -2056,6 +2056,10 @@ action wizard Start Iniciar
Perimeter Perímetro
Area Área
Drawing scale Escala del dibujo
Scale Escala
Common scales Escalas habituales
Enter a scale such as 1:50. Introduzca una escala como 1:50.
Applying a scale switches units to m / mm. Al aplicar una escala, las unidades cambian a m / mm.
Snapping Ajuste
Export measurements Exportar mediciones
Open a PDF to measure it. Abre un PDF para medirlo.
Can't render this file because it contains an unexpected character in line 6 and column 60.
+4
View File
@@ -1978,6 +1978,10 @@
Perimeter Ympärysmitta
Area Pinta-ala
Drawing scale Piirustuksen mittakaava
Scale Mittakaava
Common scales Yleiset mittakaavat
Enter a scale such as 1:50. Anna mittakaava, esimerkiksi 1:50.
Applying a scale switches units to m / mm. Mittakaavan käyttö vaihtaa yksiköiksi m / mm.
Snapping Tartunta
Export measurements Vie mittaukset
Open a PDF to measure it. Avaa PDF mitataksesi sitä.
Can't render this file because it contains an unexpected character in line 5 and column 47.
+4
View File
@@ -1971,6 +1971,10 @@
Perimeter Périmètre
Area Aire
Drawing scale Échelle du dessin
Scale Échelle
Common scales Échelles courantes
Enter a scale such as 1:50. Saisissez une échelle comme 1:50.
Applying a scale switches units to m / mm. Appliquer une échelle passe les unités en m / mm.
Snapping Accrochage
Export measurements Exporter les mesures
Open a PDF to measure it. Ouvrez un PDF pour le mesurer.
Can't render this file because it contains an unexpected character in line 6 and column 39.
+4
View File
@@ -1961,6 +1961,10 @@
Perimeter परिमाप
Area क्षेत्रफल
Drawing scale रेखाचित्र पैमाना
Scale पैमाना
Common scales सामान्य पैमाने
Enter a scale such as 1:50. 1:50 जैसा पैमाना दर्ज करें।
Applying a scale switches units to m / mm. पैमाना लागू करने पर इकाइयाँ m / mm में बदल जाती हैं।
Snapping स्नैपिंग
Export measurements मापन निर्यात करें
Open a PDF to measure it. मापने के लिए एक पीडीएफ़ खोलें।
Can't render this file because it contains an unexpected character in line 854 and column 12.
+4
View File
@@ -1961,6 +1961,10 @@
Perimeter Kerület
Area Terület
Drawing scale Rajz méretaránya
Scale Méretarány
Common scales Gyakori méretarányok
Enter a scale such as 1:50. Adjon meg egy méretarányt, például 1:50.
Applying a scale switches units to m / mm. A méretarány alkalmazása m / mm mértékegységre vált.
Snapping Illesztés
Export measurements Mérések exportálása
Open a PDF to measure it. Nyiss meg egy PDF-et a méréshez.
Can't render this file because it contains an unexpected character in line 7 and column 44.
+4
View File
@@ -1392,6 +1392,10 @@
Draw freehand Gambar bebas
Drawing Gambar
Drawing scale Skala gambar
Scale Skala
Common scales Skala umum
Enter a scale such as 1:50. Masukkan skala seperti 1:50.
Applying a scale switches units to m / mm. Menerapkan skala mengubah satuan menjadi m / mm.
Drop-down list Daftar drop-down
Duplicate pages Duplikat halaman
Edit a PDF Edit PDF
Can't render this file because it contains an unexpected character in line 483 and column 12.
+4
View File
@@ -2047,6 +2047,10 @@
Perimeter Perimetro
Area Area
Drawing scale Scala del disegno
Scale Scala
Common scales Scale comuni
Enter a scale such as 1:50. Inserisci una scala come 1:50.
Applying a scale switches units to m / mm. Applicando una scala le unità passano a m / mm.
Snapping Aggancio
Export measurements Esporta misure
Open a PDF to measure it. Apri un PDF per misurarlo.
Can't render this file because it contains an unexpected character in line 6 and column 47.
+4
View File
@@ -2054,6 +2054,10 @@
Perimeter 周長
Area 面積
Drawing scale 図面の縮尺
Scale 縮尺
Common scales よく使う縮尺
Enter a scale such as 1:50. 1:50 のように縮尺を入力してください。
Applying a scale switches units to m / mm. 縮尺を適用すると、単位が m / mm に切り替わります。
Snapping スナップ
Export measurements 測定結果を書き出す
Open a PDF to measure it. 測定するPDFを開いてください。
Can't render this file because it contains an unexpected character in line 6 and column 47.
+4
View File
@@ -1962,6 +1962,10 @@
Perimeter Периметр
Area Аудан
Drawing scale Сызба масштабы
Scale Масштаб
Common scales Жиі қолданылатын масштабтар
Enter a scale such as 1:50. 1:50 сияқты масштабты енгізіңіз.
Applying a scale switches units to m / mm. Масштабты қолдану өлшем бірліктерін m / mm етіп ауыстырады.
Snapping Түйістіру
Export measurements Өлшемдерді экспорттау
Open a PDF to measure it. Өлшеу үшін PDF құжатын ашыңыз.
Can't render this file because it contains an unexpected character in line 859 and column 9.
+4
View File
@@ -1961,6 +1961,10 @@
Perimeter 둘레
Area 면적
Drawing scale 도면 축척
Scale 축척
Common scales 자주 쓰는 축척
Enter a scale such as 1:50. 1:50과 같은 축척을 입력하세요.
Applying a scale switches units to m / mm. 축척을 적용하면 단위가 m / mm로 바뀝니다.
Snapping 스냅
Export measurements 측정값 내보내기
Open a PDF to measure it. 측정할 PDF를 여세요.
Can't render this file because it contains an unexpected character in line 854 and column 12.
+4
View File
@@ -2014,6 +2014,10 @@
Perimeter ລວງຮອບ
Area ເນື້ອທີ່
Drawing scale ມາດຕາສ່ວນຮູບແຕ້ມ
Scale ມາດຕາສ່ວນ
Common scales ມາດຕາສ່ວນທີ່ໃຊ້ທົ່ວໄປ
Enter a scale such as 1:50. ປ້ອນມາດຕາສ່ວນ ເຊັ່ນ 1:50.
Applying a scale switches units to m / mm. ການນຳໃຊ້ມາດຕາສ່ວນຈະປ່ຽນຫົວໜ່ວຍເປັນ m / mm.
Snapping ການດູດຈັບ
Export measurements ສົ່ງອອກການວັດແທກ
Open a PDF to measure it. ເປີດ PDF ເພື່ອວັດແທກ.
Can't render this file because it contains an unexpected character in line 6 and column 32.
+4
View File
@@ -1452,6 +1452,10 @@
Draw freehand Brėžti laisva ranka
Drawing Brėžinys
Drawing scale Brėžinio mastelis
Scale Mastelis
Common scales Dažni masteliai
Enter a scale such as 1:50. Įveskite mastelį, pavyzdžiui, 1:50.
Applying a scale switches units to m / mm. Pritaikius mastelį, vienetai pakeičiami į m / mm.
Drop-down list Išskleidžiamasis sąrašas
Duplicate pages Dubliuoti puslapius
Edit a PDF Redaguoti PDF
Can't render this file because it contains an unexpected character in line 406 and column 9.
+4
View File
@@ -1403,6 +1403,10 @@
Draw freehand Tegn på frihånd
Drawing Tegning
Drawing scale Tegneskala
Scale Målestokk
Common scales Vanlige målestokker
Enter a scale such as 1:50. Skriv inn en målestokk, for eksempel 1:50.
Applying a scale switches units to m / mm. Når du bruker en målestokk, byttes enhetene til m / mm.
Drop-down list Nedtrekksliste
Duplicate pages Dupliser sider
Edit a PDF Rediger en PDF
Can't render this file because it contains an unexpected character in line 8 and column 16.
+4
View File
@@ -2034,6 +2034,10 @@
Perimeter Obwód
Area Pole powierzchni
Drawing scale Skala rysunku
Scale Skala
Common scales Popularne skale
Enter a scale such as 1:50. Wpisz skalę, na przykład 1:50.
Applying a scale switches units to m / mm. Zastosowanie skali zmienia jednostki na m / mm.
Snapping Przyciąganie
Export measurements Eksportuj pomiary
Open a PDF to measure it. Otwórz PDF, aby wykonać pomiar.
Can't render this file because it contains an unexpected character in line 6 and column 47.
+4
View File
@@ -1775,6 +1775,10 @@
Perimeter Perímetro
Area Área
Drawing scale Escala de desenho
Scale Escala
Common scales Escalas comuns
Enter a scale such as 1:50. Digite uma escala como 1:50.
Applying a scale switches units to m / mm. Aplicar uma escala muda as unidades para m / mm.
Snapping Alinhamento inteligente
Export measurements Exportar medições
Open a PDF to measure it. Abra um PDF para medi-lo.
Can't render this file because it contains an unexpected character in line 9 and column 16.
+4
View File
@@ -1401,6 +1401,10 @@
Draw freehand Desenare liberă
Drawing Desen
Drawing scale Scara desenului
Scale Scară
Common scales Scări uzuale
Enter a scale such as 1:50. Introduceți o scară, de exemplu 1:50.
Applying a scale switches units to m / mm. Aplicarea unei scări schimbă unitățile în m / mm.
Drop-down list Listă derulantă
Duplicate pages Duplicare pagini
Edit a PDF Editare PDF
Can't render this file because it contains an unexpected character in line 3 and column 110.
+4
View File
@@ -2041,6 +2041,10 @@
Perimeter Периметр
Area Площадь
Drawing scale Масштаб чертежа
Scale Масштаб
Common scales Распространённые масштабы
Enter a scale such as 1:50. Введите масштаб, например 1:50.
Applying a scale switches units to m / mm. При применении масштаба единицы переключаются на м / мм.
Snapping Привязка
Export measurements Экспортировать измерения
Open a PDF to measure it. Откройте PDF для измерения.
Can't render this file because it contains an unexpected character in line 9 and column 16.
+4
View File
@@ -654,6 +654,10 @@
Draw your {what} below. Spodaj narišite: {what}.
Drawing Risba
Drawing scale Merilo risbe
Scale Merilo
Common scales Pogosta merila
Enter a scale such as 1:50. Vnesite merilo, na primer 1:50.
Applying a scale switches units to m / mm. Z uporabo merila se enote preklopijo na m / mm.
Drop PDFs or folders here, or choose them. Spustite datoteke PDF ali mape sem ali jih izberite.
Drop-down list Spustni seznam
Dropdown Properties Lastnosti spustnega seznama
Can't render this file because it contains an unexpected character in line 9 and column 16.
+4
View File
@@ -645,6 +645,10 @@
Draw your {what} below. Vizato {what} më poshtë.
Drawing Vizatim
Drawing scale Shkalla e vizatimit
Scale Shkalla
Common scales Shkalla të zakonshme
Enter a scale such as 1:50. Shkruani një shkallë si 1:50.
Applying a scale switches units to m / mm. Zbatimi i një shkalle i kalon njësitë në m / mm.
Drop PDFs or folders here, or choose them. Hidh PDF ose dosje këtu, ose zgjidh ato.
Drop-down list Listë rënëse
Dropdown Properties Vetitë e listës rënëse
Can't render this file because it contains an unexpected character in line 9 and column 16.
+4
View File
@@ -1960,6 +1960,10 @@ action wizard Start ప్రారంభించు
Perimeter చుట్టుకొలత
Area వైశాల్యం
Drawing scale డ్రాయింగ్ స్కేల్
Scale స్కేల్
Common scales సాధారణ స్కేల్‌లు
Enter a scale such as 1:50. 1:50 వంటి స్కేల్‌ను నమోదు చేయండి.
Applying a scale switches units to m / mm. స్కేల్‌ను వర్తింపజేస్తే యూనిట్లు m / mm‌కు మారతాయి.
Snapping స్నాపింగ్
Export measurements కొలతలను ఎగుమతి చేయి
Open a PDF to measure it. కొలవడానికి ఒక PDFను తెరవండి.
Can't render this file because it contains an unexpected character in line 13 and column 16.
+4
View File
@@ -1984,6 +1984,10 @@
Perimeter เส้นรอบรูป
Area พื้นที่
Drawing scale มาตราส่วนของแบบ
Scale มาตราส่วน
Common scales มาตราส่วนที่ใช้บ่อย
Enter a scale such as 1:50. ป้อนมาตราส่วน เช่น 1:50
Applying a scale switches units to m / mm. การใช้มาตราส่วนจะเปลี่ยนหน่วยเป็น m / mm
Snapping การยึด
Export measurements ส่งออกการวัด
Open a PDF to measure it. เปิด PDF เพื่อวัดระยะ
Can't render this file because it contains an unexpected character in line 865 and column 12.
+4
View File
@@ -1934,6 +1934,10 @@
Perimeter Çevre
Area Alan
Drawing scale Çizim ölçeği
Scale Ölçek
Common scales Yaygın ölçekler
Enter a scale such as 1:50. 1:50 gibi bir ölçek girin.
Applying a scale switches units to m / mm. Ölçek uygulamak birimleri m / mm olarak değiştirir.
Snapping Yakalama
Export measurements Ölçümleri dışa aktar
Open a PDF to measure it. Ölçmek için bir PDF açın.
Can't render this file because it contains an unexpected character in line 858 and column 9.
+4
View File
@@ -2038,6 +2038,10 @@
Perimeter Периметр
Area Площа
Drawing scale Масштаб креслення
Scale Масштаб
Common scales Поширені масштаби
Enter a scale such as 1:50. Введіть масштаб, наприклад 1:50.
Applying a scale switches units to m / mm. Застосування масштабу перемикає одиниці на м / мм.
Snapping Прив’язування
Export measurements Експортувати вимірювання
Open a PDF to measure it. Відкрийте PDF для вимірювання.
Can't render this file because it contains an unexpected character in line 6 and column 47.
+4
View File
@@ -1441,6 +1441,10 @@ theme Appearance Giao diện
Draw freehand Vẽ tự do
Drawing Bản vẽ
Drawing scale Tỷ lệ bản vẽ
Scale Tỷ lệ
Common scales Tỷ lệ thông dụng
Enter a scale such as 1:50. Nhập một tỷ lệ như 1:50.
Applying a scale switches units to m / mm. Áp dụng tỷ lệ sẽ chuyển đơn vị sang m / mm.
Drop-down list Danh sách thả xuống
Duplicate pages Nhân bản trang
Edit a PDF Chỉnh sửa một PDF
Can't render this file because it contains an unexpected character in line 493 and column 12.
+4
View File
@@ -1910,6 +1910,10 @@
Perimeter 周长
Area 面积
Drawing scale 图面比例
Scale 比例
Common scales 常用比例
Enter a scale such as 1:50. 请输入比例,例如 1:50。
Applying a scale switches units to m / mm. 应用比例会将单位切换为 m / mm。
Snapping 吸附
Export measurements 导出测量结果
Open a PDF to measure it. 打开 PDF 以进行测量。
Can't render this file because it contains an unexpected character in line 9 and column 16.
+4
View File
@@ -2041,6 +2041,10 @@
Perimeter 周長
Area 面積
Drawing scale 圖面比例
Scale 比例
Common scales 常用比例
Enter a scale such as 1:50. 請輸入比例,例如 1:50。
Applying a scale switches units to m / mm. 套用比例會將單位切換為 m / mm。
Snapping 吸附
Export measurements 匯出測量結果
Open a PDF to measure it. 開啟 PDF 以進行測量。
Can't render this file because it contains an unexpected character in line 9 and column 16.
+218 -2
View File
@@ -55,6 +55,10 @@ pub struct MeasureView {
pub unit: String,
/// The unit picked for the page side of the scale; `None` follows `unit`.
pub page_unit: Option<PageUnit>,
/// The "1:N" field of the drawing scale, as typed.
pub ratio_text: String,
/// The "1:N" field was edited and not yet applied.
ratio_dirty: bool,
pub precision: u8,
pub name: String,
pub whole_page: bool,
@@ -83,6 +87,8 @@ impl Default for MeasureView {
real_distance: 1.0,
unit: "in".into(),
page_unit: None,
ratio_text: String::new(),
ratio_dirty: false,
precision: 2,
name: "Drawing scale".into(),
whole_page: true,
@@ -99,6 +105,59 @@ impl Default for MeasureView {
pub(crate) fn page_unit_of(picked: Option<PageUnit>, unit: &str, display: crate::units::Unit) -> PageUnit {
picked.or_else(|| PageUnit::from_label(unit)).or_else(|| PageUnit::from_label(display.label())).unwrap_or(PageUnit::Pt)
}
/// Drawing scales an architect's rule offers, as the N of 1:N.
pub(crate) const RATIOS: [f64; 10] = [1.0, 5.0, 10.0, 20.0, 25.0, 50.0, 100.0, 200.0, 500.0, 1000.0];
/// "1:200", "1/200", "1 : 200" or a bare "200" (a comma works as the decimal mark), as the N of 1:N.
/// `None` for anything else, for zero, negative or absurd values.
pub(crate) fn parse_ratio(text: &str) -> Option<f64> {
let text = text.trim().replace(',', ".");
let n = match text.split_once([':', '/']) {
Some((one, n)) => n.trim().parse::<f64>().ok()? / one.trim().parse::<f64>().ok()?,
None => text.parse::<f64>().ok()?,
};
RATIO_RANGE.contains(&n).then_some(n)
}
/// The N of 1:N this panel accepts. The lower bound keeps the real distance (N / 1000 m) within the
/// minimum of its field.
const RATIO_RANGE: std::ops::RangeInclusive<f64> = 1e-3..=1e9;
/// "1:200", or "1:2.5" for a ratio that isn't a whole number: the number as it is, so the field shows exactly what applies.
pub(crate) fn format_ratio(n: f64) -> String {
format!("1:{n}")
}
/// The N of 1:N the scale fields stand for, when they are a plain drawing scale (real distance in m or mm).
fn ratio_of_fields(state: &MeasureView, display: crate::units::Unit) -> Option<f64> {
let real_mm = match state.unit.trim() {
"m" => state.real_distance * 1000.0,
"mm" => state.real_distance,
_ => return None,
};
let paper_mm = state.drawing_points * page_unit_of(state.page_unit, &state.unit, display).points() * 25.4 / 72.0;
let n = real_mm / paper_mm;
// Float noise from the unit conversions is rounded away; anything that isn't a clean ratio isn't one.
let clean = (n * 1e6).round() / 1e6;
(RATIO_RANGE.contains(&clean) && (clean - n).abs() <= 1e-9 * n).then_some(clean)
}
/// Make the "1:N" field and the common scale picked in its list say what the scale fields now hold: the ratio, or nothing.
fn sync_ratio_text(state: &mut MeasureView, display: crate::units::Unit) {
state.ratio_text = ratio_of_fields(state, display).map(format_ratio).unwrap_or_default();
state.ratio_dirty = false;
}
/// What the scale fields hold, to notice a hand edit.
fn scale_fields(state: &MeasureView) -> (f64, f64, String, Option<PageUnit>) {
(state.drawing_points, state.real_distance, state.unit.clone(), state.page_unit)
}
/// Fill the scale fields for 1:N: 1 mm on the page is N mm, shown in metres. Returns `false`, changing nothing, when `n` is not a usable ratio.
pub(crate) fn set_ratio(state: &mut MeasureView, n: f64) -> bool {
if !RATIO_RANGE.contains(&n) {
return false;
}
state.unit = "m".into();
state.page_unit = Some(PageUnit::Mm);
state.drawing_points = 1.0;
state.real_distance = n / 1000.0;
state.ratio_text = format_ratio(n);
true
}
impl MeasureView {
pub fn cancel(&mut self) {
self.points.clear();
@@ -330,6 +389,7 @@ fn panel_body(app: &mut PdfCraftApp, ui: &mut egui::Ui, _t: &Tokens) {
state.page_unit = None;
state.precision = scale.precision;
}
sync_ratio_text(state, units);
}
ui.label(tl!("Click vertices. Enter or double-click finishes a perimeter or area. Click the first point to close an area."));
ui.label(tl!("Shift constrains to 45 degrees. Backspace removes a vertex. Escape cancels."));
@@ -363,7 +423,28 @@ fn panel_body(app: &mut PdfCraftApp, ui: &mut egui::Ui, _t: &Tokens) {
ui.add(egui::Slider::new(&mut state.sensitivity, 1.0..=20.0).text(tl!("Sensitivity")));
}
ui.separator();
ui.label(tl!("Drawing scale"));
// "1:N" fills the fields below and applies at once, as does picking a common scale.
let mut chosen = None;
let fields = scale_fields(state);
ui.horizontal(|ui| {
ui.label(tl!("Drawing scale")).on_hover_text(tl!("Applying a scale switches units to m / mm."));
let typed = ui.add(crate::widgets::line(&mut state.ratio_text).hint_text("1:100").desired_width(56.0).char_limit(16));
typed.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::TextEdit, true, tl!("Scale")));
state.ratio_dirty |= typed.changed();
if typed.lost_focus() && std::mem::take(&mut state.ratio_dirty) {
match parse_ratio(&state.ratio_text) {
Some(n) => chosen = Some(n),
None => state.error = Some(tl!("Enter a scale such as 1:50.").into()),
}
}
egui::ComboBox::from_id_salt("measure-common-scales").selected_text(tl!("Common scales")).width(20.0).show_ui(ui, |ui| {
for n in RATIOS {
if ui.selectable_label(state.ratio_text == format_ratio(n), format_ratio(n)).clicked() {
chosen = Some(n);
}
}
});
});
ui.horizontal(|ui| {
ui.add(egui::DragValue::new(&mut state.drawing_points).range(0.000001..=1e9));
let page_unit = page_unit_of(state.page_unit, &state.unit, units);
@@ -382,6 +463,10 @@ fn panel_body(app: &mut PdfCraftApp, ui: &mut egui::Ui, _t: &Tokens) {
ui.add(crate::widgets::line(&mut state.unit).desired_width(64.0).char_limit(12));
ui.add(egui::DragValue::new(&mut state.precision).range(0..=6).prefix(format!("{} ", tl!("Decimals"))));
});
// A hand edit of the fields leaves the 1:N field showing the old ratio: say the new one.
if scale_fields(state) != fields {
sync_ratio_text(state, units);
}
if ui.button(tl!("Calibrate from two points")).clicked() {
tool = Some(Tool::Calibrate);
}
@@ -427,7 +512,16 @@ fn panel_body(app: &mut PdfCraftApp, ui: &mut egui::Ui, _t: &Tokens) {
}
}
let export = ui.button(tl!("Export measurements as CSV")).clicked();
if apply && let Some(info) = info {
if let Some(n) = chosen
&& set_ratio(state, n)
{
state.error = None;
}
// Apply scale, or a 1:N typed or picked: the fields are applied to the document at once.
if (apply || chosen.is_some())
&& doc.allows_annotation()
&& let Some(info) = info
{
let result =
doc.measurement_to_user(page, [0.0, 0.0]).and_then(|a| doc.measurement_to_user(page, [1.0, 0.0]).map(|b| (a, b))).and_then(|(a, b)| {
let page_unit = page_unit_of(state.page_unit, &state.unit, units);
@@ -513,3 +607,125 @@ fn export_csv(app: &mut PdfCraftApp) {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
/// What Apply scale builds from the panel's fields, then a distance read with it.
fn measured(state: &MeasureView, from: Point, to: Point) -> f64 {
let page_unit = page_unit_of(state.page_unit, &state.unit, crate::units::Unit::Points);
let scale = Scale::ratio([0.0, 0.0], [1.0, 0.0], state.drawing_points, page_unit, state.real_distance, &state.unit, 4).expect("scale");
measure::reading(Kind::Distance, &[from, to], &scale).expect("reading").value
}
#[test]
fn typed_ratios_are_read_leniently() {
assert_eq!(parse_ratio("1:200"), Some(200.0));
assert_eq!(parse_ratio(" 1 : 50 "), Some(50.0));
assert_eq!(parse_ratio("1/20"), Some(20.0));
assert_eq!(parse_ratio("250"), Some(250.0));
assert_eq!(parse_ratio("1:2,5"), Some(2.5));
assert_eq!(parse_ratio("2:1"), Some(0.5));
assert_eq!(parse_ratio("1:0.001"), Some(1e-3));
}
#[test]
fn invalid_ratios_are_refused() {
for bad in [
"", " ", ":", "1:", ":50", "1:0", "0", "-5", "1:-5", "0:5", "abc", "1:abc", "1:50:2", "1:inf", "NaN", "1e12", "1:1e12", "1:0.0001",
"1:1e-6",
] {
assert_eq!(parse_ratio(bad), None, "{bad:?}");
}
}
#[test]
fn ratios_are_written_as_people_say_them() {
assert_eq!(format_ratio(50.0), "1:50");
assert_eq!(format_ratio(2.5), "1:2.5");
assert_eq!(format_ratio(0.5), "1:0.5");
// Nothing is rounded away: the field reads the ratio that applies.
assert_eq!(format_ratio(1.234), "1:1.234");
assert_eq!(format_ratio(0.001), "1:0.001");
assert_eq!(parse_ratio(&format_ratio(333.333333)), Some(333.333333));
for n in RATIOS {
assert_eq!(parse_ratio(&format_ratio(n)), Some(n));
}
}
#[test]
fn every_offered_ratio_reads_paper_length_times_n_in_metres_whatever_the_unit_was() {
for unit in ["mm", "cm", "m", "km", "in", "ft"] {
for n in RATIOS {
let mut state = MeasureView { unit: unit.into(), ..Default::default() };
assert!(set_ratio(&mut state, n));
assert_eq!(state.unit, "m");
// 72 pt is one inch of paper: 25.4 mm x N, shown in metres.
let v = measured(&state, [0.0, 0.0], [0.0, 72.0]);
let expected = 25.4 * n / 1000.0;
assert!((v - expected).abs() < 1e-9 * expected.max(1.0), "1:{n} in {unit} gave {v}, wanted {expected}");
}
}
}
#[test]
fn any_starting_unit_becomes_metres() {
let mut state = MeasureView { unit: "yards?".into(), ..Default::default() };
assert!(set_ratio(&mut state, 50.0));
assert_eq!(state.unit, "m");
// 1 mm of paper at 1:50 is 50 mm.
let v = measured(&state, [0.0, 0.0], [72.0 / 25.4, 0.0]);
assert!((v - 0.05).abs() < 1e-9, "{v}");
}
#[test]
fn the_smallest_ratio_keeps_the_real_distance_within_its_field() {
let mut state = MeasureView::default();
assert!(set_ratio(&mut state, parse_ratio("1:0.001").expect("ratio")));
// The real-distance field accepts 0.000001 and no less.
assert!(state.real_distance >= 0.000001, "{}", state.real_distance);
assert!(!set_ratio(&mut state, 1e-4));
}
#[test]
fn the_ratio_field_follows_the_scale_fields() {
let display = crate::units::Unit::Points;
let mut state = MeasureView::default();
for n in [50.0, 2.5, 1.234, 0.001, 1e9] {
assert!(set_ratio(&mut state, n));
sync_ratio_text(&mut state, display);
assert_eq!(state.ratio_text, format_ratio(n), "1:{n}");
}
// A hand edit that is still a drawing scale reads as its ratio, in another page unit too.
assert!(set_ratio(&mut state, 50.0));
state.page_unit = Some(PageUnit::Cm);
sync_ratio_text(&mut state, display);
assert_eq!(state.ratio_text, "1:5");
state.page_unit = Some(PageUnit::Mm);
state.real_distance = 0.1;
sync_ratio_text(&mut state, display);
assert_eq!(state.ratio_text, "1:100");
// One that isn't (another unit, or no ratio at all) empties it.
state.unit = "ft".into();
sync_ratio_text(&mut state, display);
assert_eq!(state.ratio_text, "");
state.unit = "m".into();
state.real_distance = 0.1 / 3.0;
sync_ratio_text(&mut state, display);
assert_eq!(state.ratio_text, "");
state.drawing_points = 0.0;
sync_ratio_text(&mut state, display);
assert_eq!(state.ratio_text, "");
}
#[test]
fn an_unusable_ratio_changes_nothing() {
let mut state = MeasureView::default();
let before = (state.drawing_points, state.real_distance, state.unit.clone(), state.page_unit);
for n in [0.0, -1.0, f64::NAN, f64::INFINITY] {
assert!(!set_ratio(&mut state, n));
}
assert_eq!((state.drawing_points, state.real_distance, state.unit.clone(), state.page_unit), before);
}
}
+52
View File
@@ -1364,6 +1364,58 @@ fn a_1_to_50_scale_in_millimetres_measures_a_10_mm_line_as_500_mm() {
assert_eq!(measurements[0].reading.unit, "mm");
}
#[test]
fn a_plan_scale_picked_or_typed_applies_at_once() {
use pdfcraft_engine::measure::{self, Kind};
let (mut h, c) = harness_pages(1);
let doc = h.state().views[0].id;
ok(&mut h, &c, "ui.command", json!({"id":"measure.scale"}));
h.run_steps(3);
h.state_mut().views[0].measure.unit = "m".into();
h.run_steps(2);
// 60 mm of paper.
let line = [[20.0, 20.0], [20.0 + 60.0 / 25.4 * 72.0, 20.0]];
let read = |h: &Harness<'static, PdfCraftApp>| {
let scale = h.state().session.get(doc).unwrap().measurement_scale(0, line[0]).unwrap();
(measure::reading(Kind::Distance, &line, &scale).unwrap().label, scale.ratio)
};
// Picked from the common scales: no Apply click.
h.get_by(|n| n.role() == egui::accesskit::Role::ComboBox && n.value().is_some_and(|v| v == "Common scales")).click();
h.run_steps(2);
h.get_by_label("1:200").click_accesskit();
h.run_steps(3);
assert_eq!(read(&h), ("12.00 m".to_string(), "1 mm = 0.2 m".to_string()));
// Typed, in the other notations; leaving the field applies.
fn field<'a>(h: &'a Harness<'static, PdfCraftApp>) -> egui_kittest::Node<'a> {
h.get_by_role_and_label(egui::accesskit::Role::TextInput, "Scale")
}
for (typed, label) in [("1/50", "3.00 m"), ("1:100", "6.00 m")] {
field(&h).click();
h.run_steps(1);
h.state_mut().views[0].measure.ratio_text.clear();
field(&h).type_text(typed);
h.run_steps(2);
h.key_press(egui::Key::Enter);
h.run_steps(3);
assert_eq!(read(&h).0, label, "{typed}");
}
// Nonsense is refused with a message and leaves the page scale alone.
let before = read(&h);
field(&h).click();
h.run_steps(1);
h.state_mut().views[0].measure.ratio_text.clear();
field(&h).type_text("1:0");
h.run_steps(2);
h.key_press(egui::Key::Enter);
h.run_steps(3);
assert_eq!(read(&h), before);
assert!(h.state().views[0].measure.error.is_some());
// The scale is one undoable edit.
ok(&mut h, &c, "ui.command", json!({"id":"edit.undo"}));
h.run_steps(2);
assert_ne!(read(&h), before);
}
#[test]
fn docking_pointer_group_float_close_restore_and_document_commands() {
use pdfcraft_ui_egui::docking::Panel;
+1 -1
View File
@@ -4280,7 +4280,7 @@ milestone = "M12"
status = "partial"
commands = ["measure.distance"]
tools = ["measure_distance"]
evidence = ["crates/automation/tests/it/automation.rs::measurements_calibrate_draw_save_reopen_and_export", "crates/measure/src/tests.rs::calibrated_lengths_concave_areas_and_axis_scales", "crates/ui-egui/tests/it/control.rs::measurement_tools_draw_live_calibrate_save_and_export"]
evidence = ["crates/automation/tests/it/automation.rs::measurements_calibrate_draw_save_reopen_and_export", "crates/measure/src/tests.rs::calibrated_lengths_concave_areas_and_axis_scales", "crates/ui-egui/tests/it/control.rs::measurement_tools_draw_live_calibrate_save_and_export", "crates/ui-egui/tests/it/control.rs::a_plan_scale_picked_or_typed_applies_at_once", "crates/ui-egui/src/measure_ui.rs::typed_ratios_are_read_leniently"]
notes = "Two-point distance annotations with saved numeric captions and rectilinear scales. Partial: only single-unit decimal formats; compound (ft-in) and fractional formats are not supported (listed as unsupported, data kept), no leader lines or caption placement options, geospatial scales unsupported, and interop with Acrobat-authored measurements (reading theirs, Acrobat reading ours) is untested."
[[feature]]