diff --git a/src/mark-shapes.typ b/src/mark-shapes.typ new file mode 100644 index 00000000..5204f84f --- /dev/null +++ b/src/mark-shapes.typ @@ -0,0 +1,74 @@ +#import "drawable.typ" +#import "path-util.typ" + +// Calculate triangular tip offset, depending on the strokes +// join type. +// +// The angle is calculated for an isosceles triangle of base style.widh +// and height style.length +#let calculate-tip-offset(style) = { + if style.stroke.join == "round" { + return style.stroke.thickness / 2 + } + + if style.length == 0 { + return 0 + } + + let angle = calc.atan(style.width / (2 * style.length) / if style.harpoon { 2 } else { 1 } ) * 2 + // If the miter length divided by the stroke width exceeds + // the stroke miter limit then the miter join is converted to a bevel. + // See: https://svgwg.org/svg2-draft/painting.html#LineJoin + if style.stroke.join == "miter" { + let angle = calc.abs(angle) + let miter-limit = 1 / calc.sin(angle / 2) + if miter-limit <= style.stroke.miter-limit { + return miter-limit * (style.stroke.thickness / 2) + } + } + + // style.stroke.join must be "bevel" + return calc.sin(angle/2) * (style.stroke.thickness / 2) +} + + +// Dictionary of built-in mark styles +// +// (style) => (drawables:, tip-offset:, distance:) +#let marks = ( + triangle: (style) => ( + drawables: drawable.path( + path-util.line-segment( + ( + (0, 0), + (style.length, style.width/2), + if style.harpoon { (style.length, 0) } else { (style.length, -style.width/2) } + ) + ), + close: true, + fill: style.fill, + stroke: style.stroke + ), + tip-offset: calculate-tip-offset(style), + distance: style.length + ), + stealth: (style) => ( + drawables: drawable.path( + path-util.line-segment( + ( + (0, 0), + (style.length, style.width/2), + (style.length - style.inset, 0), + if not style.harpoon { + (style.length, -style.width/2) + } + ).filter(c => c != none) + ), + stroke: style.stroke, + close: true, + fill: style.fill + ), + distance: style.length - style.inset, + tip-offset: calculate-tip-offset(style) + ) +) diff --git a/src/mark.typ b/src/mark.typ index 7787edb3..5b32289d 100644 --- a/src/mark.typ +++ b/src/mark.typ @@ -1,86 +1,14 @@ #let typst-length = length -#import "bezier.typ" #import "drawable.typ" #import "vector.typ" #import "matrix.typ" #import "util.typ" #import "path-util.typ" #import "styles.typ" +#import "mark-shapes.typ" #let check-mark(style) = (style.start, style.end, style.symbol).any(v => v != none) -// Calculate triangular tip offset, depending on the strokes -// join type. -// -// The angle is calculated for an isosceles triangle of base style.widh -// and height style.length -#let calculate-tip-offset(style) = { - if style.stroke.join == "round" { - return style.stroke.thickness / 2 - } - - if style.length == 0 { - return 0 - } - - let angle = calc.atan(style.width / (2 * style.length) / if style.harpoon { 2 } else { 1 } ) * 2 - // If the miter length divided by the stroke width exceeds - // the stroke miter limit then the miter join is converted to a bevel. - // See: https://svgwg.org/svg2-draft/painting.html#LineJoin - if style.stroke.join == "miter" { - let angle = calc.abs(angle) - let miter-limit = 1 / calc.sin(angle / 2) - if miter-limit <= style.stroke.miter-limit { - return miter-limit * (style.stroke.thickness / 2) - } - } - - // style.stroke.join must be "bevel" - return calc.sin(angle/2) * (style.stroke.thickness / 2) -} - -// Dictionary of built-in mark styles -// -// (style) => (drawables:, tip-offset:, distance:) -#let marks = ( - triangle: (style) => ( - drawables: drawable.path( - path-util.line-segment( - ( - (0, 0), - (style.length, style.width/2), - if style.harpoon { (style.length, 0) } else { (style.length, -style.width/2) } - ) - ), - close: true, - fill: style.fill, - stroke: style.stroke - ), - tip-offset: calculate-tip-offset(style), - distance: style.length - ), - stealth: (style) => ( - drawables: drawable.path( - path-util.line-segment( - ( - (0, 0), - (style.length, style.width/2), - (style.length - style.inset, 0), - if not style.harpoon { - (style.length, -style.width/2) - } - ).filter(c => c != none) - ), - stroke: style.stroke, - close: true, - fill: style.fill - ), - distance: style.length - style.inset, - tip-offset: calculate-tip-offset(style) - ) -) - - #let process-style(ctx, style, root) = { let base-style = ( symbol: auto, @@ -180,7 +108,7 @@ /// - drawables (drawables): The transformed drawables of the mark. /// - distance: The distance between the tip of the mark and the end. #let place-mark(style, pos, angle) = { - let (drawables, distance, tip-offset) = (marks.at(style.symbol))(style) + let (drawables, distance, tip-offset) = (mark-shapes.marks.at(style.symbol))(style) return ( drawables: drawable.apply-transform( @@ -205,7 +133,7 @@ if style.symbol == none { continue } - let mark = (marks.at(style.symbol))(style) + let mark = (mark-shapes.marks.at(style.symbol))(style) mark.length = mark.distance + mark.tip-offset let pos = path-util.point-on-path(